<pre class="metadata">
Shortname: webxr
Title: WebXR Device API
Group: immersivewebwg
Status: ED
TR: https://www.w3.org/TR/webxr/
ED: https://immersive-web.github.io/webxr/
Previous Version: https://www.w3.org/TR/2020/WD-webxr-20200724/
Repository: immersive-web/webxr
Level: 1
Mailing List Archives: https://lists.w3.org/Archives/Public/public-immersive-web/

!Participate: <a href="https://github.com/immersive-web/webxr/issues/new">File an issue</a> (<a href="https://github.com/immersive-web/webxr/issues">open issues</a>)
!Participate: <a href="https://lists.w3.org/Archives/Public/public-immersive-web/">Mailing list archive</a>
!Participate: <a href="irc://irc.w3.org:6665/">W3C's #immersive-web IRC</a>

Editor: Brandon Jones 87824, Google http://google.com/, bajones@google.com
Former Editor: Nell Waliczek 93109, Amazon [Microsoft until 2018] https://amazon.com/, nhw@amazon.com
Editor: Manish Goregaokar 109489, Invited Expert [Mozilla until 2020], manishsmail@gmail.com

Abstract: This specification describes support for accessing virtual reality (VR) and augmented reality (AR) devices, including sensors and head-mounted displays, on the Web.

Markup Shorthands: markdown yes
</pre>

<pre class="link-defaults">
spec:infra;
    type:dfn; text:string
    type:dfn; text:tuple
    type:dfn; text:continue
spec:permissions-1;
    type:dfn; text:powerful feature
spec:webidl;
    type:dfn; text:new
spec:webxr-ar-module-1;
    type:dfn; text:first-person observer view
spec:html; type:interface; text:Navigator
</pre>

<pre class="anchors">
spec:infra; urlPrefix: https://infra.spec.whatwg.org/
    type:dfn; for:list; text:extend; url: list-extend
spec: High Resolution Time; urlPrefix: https://www.w3.org/TR/hr-time/
    type: typedef; text: DOMHighResTimeStamp; url: dom-domhighrestimestamp
spec: WebGL; urlPrefix: https://www.khronos.org/registry/webgl/specs/latest/1.0/
    type: interface; text: WebGLFramebuffer; url: WebGLFramebuffer
    type: interface; text: WebGLRenderingContext; url: WebGLRenderingContext
    type: interface; text: WebGLRenderingContextBase; url: WebGLRenderingContextBase
    type: interface; text: WebGLObject; url: WebGLObject
    type: typedef; text: INVALID_OPERATION; url: WebGLRenderingContextBase
    type: typedef; text: INVALID_FRAMEBUFFER_OPERATION; url: WebGLRenderingContextBase
    type: typedef; text: FRAMEBUFFER_UNSUPPORTED; url: WebGLRenderingContextBase
    type: method; text: clear; url: 5.14.11
    type: method; text: drawArrays; url: 5.14.11
    type: method; text: drawElements; url: 5.14.11
    type: method; text: uniformMatrix4fv; url: 5.14.10
    type: method; text: framebufferTexture2D;  url: 5.14.6
    type: method; text: framebufferRenderbuffer;  url: 5.14.6
    type: method; text: getFramebufferAttachmentParameter;  url: 5.14.6
    type: method; text: deleteFramebuffer;  url: 5.14.6
    type: method; text: checkFramebufferStatus;  url: 5.14.6
    type: attribute; text: statusMessage; for: WebGLContextEvent; url: 5.15.1
    type: dictionary; text: WebGLContextAttributes; url: #WebGLContextAttributes
    type: dfn; text: Create the WebGL context; url:#2.1
    type: dfn; text: default framebuffer; url:#2.2
    type: dfn; text: WebGL viewport; url:#5.14.4
    type: dfn; text: WebGL context lost flag; url:#webgl-context-lost-flag
    type: dfn; text: handle the context loss; url:#CONTEXT_LOST
    type: dfn; text: Restore the context; url: #restore-the-drawing-buffer
    type: dfn; text: actual context parameters; url: #actual-context-parameters
    type: dfn; text: create a drawing buffer; url: #create-a-drawing-buffer
    type: dfn; text: WebGL task source; url: #5.15
    type: dfn; text: canvas; for: WebGLRenderingContext; url: context-canvas
    type: dfn; text: webgl context lost flag; for: WebGLRenderingContext; url: webgl-context-lost-flag
    type: dfn; text: Fire a WebGL context event; url: fire-a-webgl-context-event
    type: dfn; text: invalidated; for: WebGLObject; url: #webgl-object-invalidated-flag
spec: WebGL 2.0; urlPrefix: https://www.khronos.org/registry/webgl/specs/latest/2.0/
    type: interface; text: WebGL2RenderingContext; url: WebGL2RenderingContext
spec: Orientation Sensor; urlPrefix: https://w3c.github.io/orientation-sensor/
    type: interface; text: AbsoluteOrientationSensor
    type: interface; text: RelativeOrientationSensor
spec: WebIDL; urlPrefix: https://heycam.github.io/webidl/#
    type: dfn; text: invoke the Web IDL callback function; url:invoke-a-callback-function
spec:html; urlPrefix: https://html.spec.whatwg.org/multipage/
    type: method; for:HTMLCanvasElement; text:getContext(contextId); url: canvas.html#dom-canvas-getcontext
    type: method; for:Window; text:requestAnimationFrame(callback); url: imagebitmap-and-animations.html#dom-animationframeprovider-requestanimationframe
    type: dfn; text: currently focused area; url: interaction.html#currently-focused-area-of-a-top-level-browsing-context
    type: dfn; text: responsible; url: webappapis.html#responsible-document
    type: dfn; text: rendering opportunity; url: webappapis.html#rendering-opportunity
    type: dfn; text: current realm; url: webappapis.html#current
    type: dfn; text: same origin-domain; url: origin.html#same-origin-domain
    type: dfn; text: browsing context; url: browsers.html#browsing-context
    type: dfn; text: associated document; url: window-object.html#concept-document-window
spec: SecureContexts; urlPrefix: https://w3c.github.io/webappsec-secure-contexts/#
    type: dfn; text: secure context; url: secure-contexts
spec: PointerEvents; urlPrefix: https://www.w3.org/TR/pointerevents/#
    type: dfn; text: primary pointer; url: dfn-primary-pointer
spec: PageVisibility; urlPrefix: https://www.w3.org/TR/page-visibility-2/#
    type: dfn; text: visibilityState; url: visibilitystate-attribute
    type: dfn; text: hidden; url: dom-visibilitystate-hidden

spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
    type: method; text: IsDetachedBuffer; url: sec-isdetachedbuffer
    type: dfn; text: ?; url: sec-returnifabrupt-shorthands
    type: dfn; text: Realm; url: realm
    type: abstract-op; text: ToString; url: sec-tostring
spec: dom; urlPrefix: https://dom.spec.whatwg.org/#
    type:algorithm; text:fire an event; url: concept-event-fire

spec: requestidlecallback; urlPrefix: https://w3c.github.io/requestidlecallback/#
    type:method; text: requestIdleCallback(); for: Window; url:the-requestidlecallback-method
</pre>

<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="favicon-96x96.png">

<style>
  .head h1:after {
    content: url(images/spec-logo.png);
    float: right;
  }

  .unstable::before {
    content: "This section is not stable";
    display: block;
    font-weight: bold;
    text-align: right;
    color: red;
  }
  .unstable {
    border: thin solid pink;
    border-radius: .5em;
    padding: .5em;
    margin: .5em calc(-0.5em - 1px);
    background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='290'><text transform='rotate(-45)' text-anchor='middle' font-family='sans-serif' font-weight='bold' font-size='70' y='210' opacity='.1'>Unstable</text></svg>");
    background-repeat: repeat;
    background-color: #FFF4F4;
  }
  .unstable h3:first-of-type {
    margin-top: 0.5rem;
  }

  .unstable.example:not(.no-marker)::before {
    content: "Example " counter(example) " (Unstable)";
    float: none;
  }

  .non-normative::before {
    content: "This section is non-normative.";
    font-style: italic;
  }
  .tg {
    border-collapse: collapse;
    border-spacing: 0;
  }
  .tg th {
    border-style: solid;
    border-width: 1px;
    background: #90b8de;
    color: #fff;
    font-family: sans-serif;
    font-weight: bold;
    border-color: grey;
  }
  .tg td {
    padding: 4px 5px;
    background-color: rgb(221, 238, 255);
    font-family: monospace;
    border-style: solid;
    border-width: 1px;
    border-color: grey;
    overflow: hidden;
    word-break: normal;
  }
</style>

Introduction {#intro}
============

<section class="non-normative">

Hardware that enables Virtual Reality (VR) and Augmented Reality (AR) applications are now broadly available to consumers, offering an immersive computing platform with both new opportunities and challenges. The ability to interact directly with immersive hardware is critical to ensuring that the web is well equipped to operate as a first-class citizen in this environment.

Immersive computing introduces strict requirements for high-precision, low-latency communication in order to deliver an acceptable experience. It also brings unique [[#security|security]] concerns for a platform like the web. The WebXR Device API provides the interfaces necessary to enable developers to build compelling, comfortable, and safe immersive applications on the web across a wide variety of hardware form factors.

Other web interfaces, such as the {{RelativeOrientationSensor}} and {{AbsoluteOrientationSensor}}, can be repurposed to surface input from some devices to polyfill the WebXR Device API in limited situations. These interfaces cannot support multiple features of high-end immersive experiences, however, such as [=6DoF=] tracking, presentation to headset peripherals, or tracked input devices.

</section>

Terminology {#terminology}
-----------

This document uses the acronym <b>XR</b> throughout to refer to the spectrum of hardware, applications, and techniques used for Virtual Reality, Augmented Reality, and other related technologies. Examples include, but are not limited to:

 * Head-mounted displays, whether they are opaque, transparent, or utilize video passthrough
 * Mobile devices with positional tracking
 * Fixed displays with head tracking capabilities

The important commonality between them being that they offer some degree of spatial tracking with which to simulate a view of virtual content.

Terms like "XR device", "XR Application", etc. are generally understood to apply to any of the above. Portions of this document that only apply to a subset of these devices will indicate so as appropriate.

The terms [=3DoF=] and [=6DoF=] are used throughout this document to describe the tracking capabilities of [=/XR devices=].

 - A <dfn>3DoF</dfn> device, short for "Three Degrees of Freedom", is one that can only track rotational movement. This is common in devices which rely exclusively on accelerometer and gyroscope readings to provide tracking. [=3DoF=] devices do not respond translational movements from the user, though they may employ algorithms to estimate translational changes based on modeling of the neck or arms.
 - A <dfn>6DoF</dfn> device, short for "Six Degrees of Freedom", is one that can track both rotation and translation, enabling for precise 1:1 tracking in space. This typically requires some level of understanding of the user's environment. That environmental understanding may be achieved via inside-out tracking, where sensors on the tracked device itself (such as cameras or depth sensors) are used to determine the device's position, or outside-in tracking, where external devices placed in the user's environment (like a camera or light emitting device) provides a stable point of reference against which the [=/XR device=] can determine its position.

Application flow {#applicationflow}
----------------

<section class="non-normative">

Most applications using the WebXR Device API will follow a similar usage pattern:

  * Query {{XRSystem/isSessionSupported()|navigator.xr.isSessionSupported()}} to determine if the desired type of XR content is supported by the hardware and UA.
  * If so, advertise the XR content to the user.
  * Wait for the window to have [=transient activation=]. This is most commonly indicated by the user clicking a button on the page indicating they want to begin viewing XR content.
  * Request an {{XRSession}} within the user activation event with {{XRSystem/requestSession()|navigator.xr.requestSession()}}.
  * If the {{XRSession}} request succeeds, use it to run a [[#frame|frame loop]] to respond to XR input and produce images to display on the [=XRSession/XR device=] in response.
  * Continue running the [[#frame|frame loop]] until the [=shut down the session|session is shut down=] by the UA or the user indicates they want to exit the XR content.

</section>

Model {#model}
=====

XR device {#xr-device-concept}
---------

An <dfn for="">XR device</dfn> is a physical unit of hardware that can present imagery to the user. On desktop clients, this is usually a headset peripheral. On mobile clients, it may represent the mobile device itself in conjunction with a viewer harness. It may also represent devices without stereo-presentation capabilities but with more advanced tracking.

An [=/XR device=] has a <dfn>list of supported modes</dfn> (a [=/list=] of [=/strings=]) that [=list/contains=] the enumeration values of {{XRSessionMode}} that the [=/XR device=] supports.

Each [=/XR device=] has a <dfn for="XR device">list of enabled features</dfn> for each {{XRSessionMode}} in its [=list of supported modes=], which is a [=/list=] of [=feature descriptors=] which MUST be initially an empty [=/list=].

The user-agent has a <dfn for="">list of immersive XR devices</dfn> (a [=/list=] of [=/XR device=]), which MUST be initially an empty [=/list=].

The user-agent has an <dfn for="">immersive XR device</dfn> (`null` or [=/XR device=]) which is initially `null` and represents the active [=/XR device=] from the [=list of immersive XR devices=]. This object MAY live on a separate thread and be updated asynchronously.

The user-agent MUST have a <dfn for="">default inline XR device</dfn>, which is an [=/XR device=] that MUST [=list/contains|contain=] {{XRSessionMode/"inline"}} in its [=list of supported modes=]. The [=/default inline XR device=] MUST NOT report any pose information, and MUST NOT report [=XR input source=]s or events other than those created by pointer events.

Note: The [=/default inline XR device=] exists purely as a convenience for developers, allowing them to use the same rendering and input logic for both inline and immersive content. The [=/default inline XR device=] does not expose any information not already available to the developer through other mechanisms on the page (such as pointer events for input), it only surfaces those values in an XR-centric format.

The user-agent MUST have a <dfn for="">inline XR device</dfn>, which is an [=/XR device=] that MUST [=list/contains|contain=] {{XRSessionMode/"inline"}} in its [=list of supported modes=]. The [=/inline XR device=] MAY be the [=immersive XR device=] if the tracking it provides makes sense to expose to inline content or the [=/default inline XR device=] otherwise.

Note: On phones, the [=/inline XR device=] may report pose information derived from the phone's internal sensors, such as the gyroscope and accelerometer. On desktops and laptops without similar sensors, the [=/inline XR device=] will not be able to report a pose, and as such should fall back to the [=/default inline XR device=]. In case the user agent is already running on an [=/XR device=], the [=/inline XR device=] will be the same device, and may support multiple [=view|views=]. User consent must be given before any tracking or input features beyond what the [=/default inline XR device=] exposes are provided.


The current values of [=list of immersive XR devices=], [=/inline XR device=], and [=immersive XR device=] MAY live on a separate thread and be updated asynchronously. These objects SHOULD NOT be directly accessed in steps that are not running [=in parallel=].

Initialization {#initialization}
==============

navigator.xr {#navigator-xr-attribute}
------------

<pre class="idl">
partial interface Navigator {
  [SecureContext, SameObject] readonly attribute XRSystem xr;
};
</pre>

The <dfn attribute for="Navigator">xr</dfn> attribute's getter MUST return the {{XRSystem}} object that is associated with it.

XRSystem {#xrsystem-interface}
----

<pre class="idl">
[SecureContext, Exposed=Window] interface XRSystem : EventTarget {
  // Methods
  Promise&lt;boolean&gt; isSessionSupported(XRSessionMode mode);
  [NewObject] Promise&lt;XRSession&gt; requestSession(XRSessionMode mode, optional XRSessionInit options = {});

  // Events
  attribute EventHandler ondevicechange;
};
</pre>

The user agent MUST create an {{XRSystem}} object when a {{Navigator}} object is created and associate it with that object.

An {{XRSystem}} object is the entry point to the API, used to query for XR features available to the user agent and initiate communication with XR hardware via the creation of {{XRSession}}s.

The user agent MUST be able to <dfn>enumerate immersive XR devices</dfn> attached to the system, at which time each available device is placed in the [=list of immersive XR devices=]. Subsequent algorithms requesting enumeration MUST reuse the cached [=list of immersive XR devices=]. Enumerating the devices [=should not initialize device tracking=]. After the first enumeration the user agent MUST begin monitoring device connection and disconnection, adding connected devices to the [=list of immersive XR devices=] and removing disconnected devices.

<div class="algorithm" data-algorithm="xr-device-selection">

Each time the [=list of immersive XR devices=] changes the user agent should <dfn>select an immersive XR device</dfn> by running the following steps:

  1. Let |oldDevice| be the [=immersive XR device=].
  1. If the [=list of immersive XR devices=] is an empty [=/list=], set the [=immersive XR device=] to `null`.
  1. If the [=list of immersive XR devices=]'s [=list/size=] is one, set the [=immersive XR device=] to the [=list of immersive XR devices=][0].
  1. Set the [=immersive XR device=] as follows:
    <dl class="switch">
      : If there are any active {{XRSession}}s and the [=list of immersive XR devices=] [=list/contains=] |oldDevice|:
      :: Set the [=immersive XR device=] to |oldDevice|.

      : Otherwise:
      :: Set the [=immersive XR device=] to a device of the user agent's choosing.

    </dl>
  1. The user agent MAY update the [=/inline XR device=] to the [=immersive XR device=] if appropriate, or the [=/default inline XR device=] otherwise.
  1. If this is the first time devices have been enumerated or |oldDevice| equals the [=immersive XR device=], abort these steps.
  1. [=Shut down the session|Shut down=] any active {{XRSession}}s.
  1. [=Queue a task=] to set the [=XR compatible=] boolean of all {{WebGLRenderingContextBase}} instances to `false`.
  1. [=Queue a task=] to [=fire an event=] named {{devicechange!!event}} on the [=context object=]'s {{Window/navigator}}'s {{Navigator/xr}}.
  1. [=Queue a task=] to fire appropriate `change` events on any {{XRPermissionStatus}} objects who are affected by the change in the [=immersive XR device=] or [=/inline XR device=].

Note: These steps should always be run [=in parallel=].

</div>

Note: The user agent is allowed to use any criteria it wishes to [=select an immersive XR device=] when the [=list of immersive XR devices=] contains multiple devices. For example, the user agent may always select the first item in the list, or provide settings UI that allows users to manage device priority. Ideally the algorithm used to select the default device is stable and will result in the same device being selected across multiple browsing sessions.

<div class="algorithm" data-algorithm="ensure-device-selected">

The user agent can <dfn>ensure an immersive XR device is selected</dfn> by running the following steps:

  1. If [=immersive XR device=] is not `null`, return [=immersive XR device=] and abort these steps.
  1. [=Enumerate immersive XR devices=].
  1. [=Select an immersive XR device=].
  1. Return the [=immersive XR device=].

Note: These steps should always be run [=in parallel=].

</div>

The <dfn attribute for="XRSystem">ondevicechange</dfn> attribute is an [=Event handler IDL attribute=] for the {{devicechange}} event type.

<div class="algorithm" data-algorithm="session-supported">
The <dfn method for="XRSystem">isSessionSupported(|mode|)</dfn> method queries if a given |mode| may be supported by the user agent and device capabilities.

When this method is invoked, it MUST run the following steps:

  1. Let |promise| be [=a new Promise=] in the [=relevant realm=] of this {{XRSystem}}.
  1. If |mode| is {{XRSessionMode/"inline"}}, [=/resolve=] |promise| with `true` and return it.
  1. If the requesting document's origin is not allowed to use the "xr-spatial-tracking" [[#permissions-policy|permissions policy]], [=reject=] |promise| with a "{{SecurityError}}" {{DOMException}} and return it.
  1. Check whether the session |mode| is supported as follows:
    <dl class="switch">
      : If the user agent and system are known to [=never support=] |mode| sessions
      :: [=/Resolve=] |promise| with `false`.

      : If the user agent and system are is known to [=usually support=] |mode| sessions
      :: |promise| MAY be [=/resolved=] with `true` provided that all instances of this user agent [=indistinguishable by user-agent string=] produce the same result here.

      : Otherwise
      :: Run the following steps [=in parallel=]:
        1. Let |device| be the result of [=ensure an immersive XR device is selected|ensuring an immersive XR device is selected=].
        1. If |device| is null, [=/resolve=] |promise| with `false` and abort these steps.
        1. If |device|'s [=list of supported modes=] does not [=list/contain=] |mode|, [=queue a task=] to [=/resolve=] |promise| with `false` and abort these steps.
        1. [=request permission to use=] the [=powerful feature=] {{PermissionName/"xr-session-supported"}} with {{XRSessionSupportedPermissionDescriptor}} with {{XRSessionSupportedPermissionDescriptor/mode}} equal to |mode|. If it returns {{PermissionState/"denied"}} [=queue a task=] to [=/resolve=] |promise| with `false` and abort these steps.
        1. [=queue a task=] to [=/resolve=] |promise| with `true`.

    </dl>
  1. Return |promise|.

</div>

<p class="note">
Note: The purpose of {{XRSystem/isSessionSupported()}} is not to report with perfect accuracy the user agent's ability to create an {{XRSession}}, but to inform the page whether or not advertising the ability to create sessions of the given mode is advised. A certain level of false-positives are expected, even when user agent checks for the presence of the necessary hardware/software prior to resolving the method. (For example, even if the appropriate hardware is present it may have given exclusive access to another application at the time a session is requested.)
</p>

It is expected that most pages with XR content will call {{XRSystem/isSessionSupported()}} early in the document lifecycle. As such, calling {{XRSystem/isSessionSupported()}} SHOULD avoid displaying any modal or otherwise intrusive UI. Calling {{XRSystem/isSessionSupported()}} MUST NOT trigger device-selection UI, MUST NOT interfere with any running XR applications on the system, and MUST NOT cause XR-related applications to launch such as system trays or storefronts.

<div class="example">
The following code checks to see if {{immersive-vr}} sessions are supported.

<pre highlight="js">
const supported = await navigator.xr.isSessionSupported('immersive-vr');
if (supported) {
  // 'immersive-vr' sessions may be supported.
  // Page should advertise support to the user.
} else {
  // 'immersive-vr' sessions are not supported.
}
</pre>
</div>

### Fingerprinting considerations ### {#issessionsupported-fingerprinting}

Because {{XRSystem/isSessionSupported()}} can be called without user activation it may be used as a fingerprinting vector despite the limited amount of information it reports.

<dfn enum-value for="PermissionName">"xr-session-supported"</dfn> [=powerful feature=] gates access to the {{XRSystem/isSessionSupported()}} API in cases where there are fingerprinting concerns.

The {{PermissionName/"xr-session-supported"}}’s permission-related algorithms and types are defined as follows:


<dl>
<dt>[=permission descriptor type=]</dt>
<dd>

<pre class="idl">
dictionary XRSessionSupportedPermissionDescriptor: PermissionDescriptor {
  XRSessionMode mode;
};
</pre>

{{PermissionDescriptor/name}} for {{XRPermissionDescriptor}} is {{PermissionName/"xr-session-supported"}}.


{{PermissionName/"xr-session-supported"}} may be granted automatically for some systems based on the criteria below.

A set of user agents is <dfn>indistinguishable by user-agent string</dfn> if they all report the same {{NavigatorID/userAgent}} and {{NavigatorID/appVersion}}. Such classes are typically identified by the browser version and platform/device being run on, but cannot be distinguished by the status of any connected external device. We can use the concept of user agents that are [=indistinguishable by user-agent string=] to properly assess fingerprinting risk.

Some user agents [=indistinguishable by user-agent string=] will <dfn>never support</dfn> sessions of a given {{XRSessionMode}}. <span class='note'>For example: User agents running on a model of phone that is known to not meet requirements for mobile AR support.</span> In these cases there is little fingerprinting risk in {{XRSystem/isSessionSupported()}} always reporting the {{XRSessionMode}} is not supported because every such device will consistently report the same value and it's assumed that device type and model can be inferred in other ways, such as through {{NavigatorID/userAgent}}. Thus, on such systems, the user-agent SHOULD automatically deny {{PermissionName/"xr-session-supported"}} for the relevant {{XRSessionMode}}.

Other user agents will [=indistinguishable by user-agent string=] <dfn>usually support</dfn> sessions of a given {{XRSessionMode}}. <span class='note'>For example: User agents known to support WebXR that run exclusively within VR headsets are likely to support {{XRSessionMode/"immersive-vr"}} sessions unless specifically blocked by the user.</span> In these cases reporting that the {{XRSessionMode}} is not supported, while accurate, would offer more uniquely identifying information about the user. As such reporting that the {{XRSessionMode}} is always available and allowing {{XRSystem/requestSession()}} to fail is more privacy-preserving while likely not being a source of confusion for the user. On such systems, the user-agent SHOULD automatically grant {{PermissionName/"xr-session-supported"}} for the relevant {{XRSessionMode}}.

User agents [=indistinguishable by user-agent string=] for which availability of XR capabilities is highly variable, such as desktop systems which support XR peripherals, present the highest fingerprinting risk. User agents on such devices SHOULD NOT automatically grant {{PermissionName/"xr-session-supported"}} in a way that allows the {{isSessionSupported()}} API to provide additional fingerprinting bits. 


<div class=note>
Note: Some acceptable approaches to handle such cases are as follows:

 - Always judging [=explicit consent=] for {{PermissionName/"xr-session-supported"}} (with a potentially cached permissions prompt or similar) when {{XRSystem/isSessionSupported()}} is called.
 - Automatically granting {{PermissionName/"xr-session-supported"}} but having {{XRSystem/isSessionSupported()}} always report `true` even on platforms which do not consistently have XR capabilities available, regardless of whether or not the appropriate hardware or software is present. This comes at the cost of user ergonomics, as it will cause pages to advertise XR content to users that cannot view it.
 - Have {{XRSystem/isSessionSupported()}} request [=explicit consent=] for {{PermissionName/"xr-session-supported"}} when the appropriate hardware is present, and when such hardware is _not_ present, return `false` after an appropriately random length of time. In such an implementation content must not be able to distinguish between cases where the user agent was not connected to XR hardware and cases where the user agent was connected to XR hardware but the user declined to provide [=explicit consent=].

</div>

Whatever the technique chosen, it MUST NOT reveal additional knowledge about connected XR hardware without [=explicit consent=].


The {{XRSystem}} object has a <dfn>pending immersive session</dfn> boolean, which MUST be initially `false`, an <dfn>active immersive session</dfn>, which MUST be initially `null`, and a <dfn>list of inline sessions</dfn>, which MUST be initially empty.

<div class="algorithm" data-algorithm="request-session">

The <dfn method for="XRSystem">requestSession(|mode|, |options|)</dfn> method attempts to initialize an {{XRSession}} for the given |mode| if possible, entering immersive mode if necessary.

When this method is invoked, the user agent MUST run the following steps:

  1. Let |promise| be [=a new Promise=] in the [=relevant realm=] of this {{XRSystem}}.
  1. Let |immersive| be `true` if |mode| is an [=immersive session=] mode, and `false` otherwise.
  1. Let |global object| be the [=relevant Global object=] for the {{XRSystem}} on which this method was invoked.
  1. Check whether the session request is allowed as follows:
    <dl class="switch">
      : If |immersive| is `true`:
      ::
        1. Check if an [=immersive session request is allowed=] for the |global object|, and if not [=reject=] |promise| with a "{{SecurityError}}" {{DOMException}} and return |promise|.
        1. If [=pending immersive session=] is `true` or [=active immersive session=] is not `null`, [=reject=] |promise| with an "{{InvalidStateError}}" {{DOMException}} and return |promise|.
        1. Set [=pending immersive session=] to `true`.

      : Otherwise:
      :: Check if an [=inline session request is allowed=] for the |global object|, and if not [=reject=] |promise| with a "{{SecurityError}}" {{DOMException}} and return |promise|.

    </dl>
  1. Run the following steps [=in parallel=]:
    1. Let |requiredFeatures| be |options|' {{XRSessionInit/requiredFeatures}}.
    1. Let |optionalFeatures| be |options|' {{XRSessionInit/optionalFeatures}}.
    1. Set |device| to the result of [=obtain the current device|obtaining the current device=] for |mode|, |requiredFeatures|, and |optionalFeatures|.
    1. [=Queue a task=] to perform the following steps:
        1. If |device| is `null` or |device|'s [=list of supported modes=] does not [=list/contain=] |mode|, run the following steps:
            1. [=Reject=] |promise| with a "{{NotSupportedError}}" {{DOMException}}.
            1. If |immersive| is `true`, set [=pending immersive session=] to `false`.
            1. Abort these steps.
        1. Let |descriptor| be an {{XRPermissionDescriptor}} initialized with |mode|, |requiredFeatures|, and |optionalFeatures|
        1. Let |status| be an {{XRPermissionStatus}}, initially `null`
        1. [=Request the xr permission=] with |descriptor| and |status|.
        1. If |status|' {{PermissionStatus/state}} is {{PermissionState/"denied"}} run the following steps:
            1. [=Reject=] |promise| with a "{{NotSupportedError}}" {{DOMException}}.
            1. If |immersive| is `true`, set [=pending immersive session=] to `false`.
            1. Abort these steps.
        1. Let |session| be a [=new=] {{XRSession}} object in the [=relevant realm=] of this {{XRSystem}}.
        1. [=Initialize the session=] with |session|, |mode|, and |device|.
        1. Potentially set the [=active immersive session=] as follows:
            <dl class="switch">
              : If |immersive| is `true`:
              :: Set the [=active immersive session=] to |session|, and set [=pending immersive session=] to `false`.

              : Otherwise:
              :: Append |session| to the [=list of inline sessions=].

            </dl>
        1. [=/Resolve=] |promise| with |session|.
        1. [=Queue a task=] to perform the following steps:
            <div class=note>Note: These steps ensure that initial {{inputsourceschange!!event}} events occur after the initial session is resolved.</div>
            1. Set |session|'s [=XRSession/promise resolved=] flag to `true`.
            1. Let |sources| be any existing input sources attached to |session|.
            1. If |sources| is non-empty, perform the following steps:
                1. Set |session|'s [=list of active XR input sources=] to |sources|.
                1. Fire an {{XRInputSourcesChangeEvent}} named {{inputsourceschange!!event}} on |session| with {{XRInputSourcesChangeEvent/added}} set to |sources|.
  1. Return |promise|.

</div>

<div class=algorithm data-algorithm="device-for-mode">

To <dfn>obtain the current device</dfn> for an {{XRSessionMode}} |mode|, |requiredFeatures|, and |optionalFeatures| the user agent MUST run the following steps:

  1. Choose |device| as follows:
      <dl class="switch">
        : If |mode| is an [=immersive session=] mode:
        :: Set |device| to the result of [=ensure an immersive XR device is selected|ensuring an immersive XR device is selected=].

        : Else if |requiredFeatures| or |optionalFeatures| are not empty:
        :: Set |device| to the [=/inline XR device=].

        : Otherwise:
        :: Set |device| to the [=/default inline XR device=].

      </dl>
  1. Return |device|.

Note: These steps should always be run [=in parallel=].

</div>

<div class="example">
The following code attempts to retrieve an {{immersive-vr}} {{XRSession}}.

<pre highlight="js">
const xrSession = await navigator.xr.requestSession("immersive-vr");
</pre>
</div>

XRSessionMode {#xrsessionmode-enum}
-------------

The {{XRSessionMode}} enum defines the modes that an {{XRSession}} can operate in.

<pre class="idl">
enum XRSessionMode {
  "inline",
  "immersive-vr",
  "immersive-ar"
};
</pre>

  - A session mode of <dfn enum-value for="XRSessionMode">inline</dfn> indicates that the session's output will be shown as an element in the HTML document. {{inline}} session content MUST be displayed in mono (i.e., with a single [=view=]). It MAY allow for [=viewer=] tracking. User agents MUST allow {{inline}} sessions to be created.
  - A session mode of <dfn enum-value for="XRSessionMode">immersive-vr</dfn> indicates that the session's output will be given [=exclusive access=] to the [=immersive XR device=] display and that content <b>is not</b> intended to be integrated with the user's environment.
  - The behavior of the <dfn enum-value for="XRSessionMode">immersive-ar</dfn> session mode is defined in the <a href="https://immersive-web.github.io/webxr-ar-module/">WebXR AR Module</a> and MUST NOT be added to the [=immersive XR device=]'s [=list of supported modes=] unless the UA implements that module.

In this document, the term <dfn>inline session</dfn> is synonymous with an {{inline}} session and the term <dfn>immersive session</dfn> refers to either an {{immersive-vr}} or {{immersive-ar}} session.

[=Immersive sessions=] MUST provide some level of [=viewer=] tracking, and content MUST be shown at the proper scale relative to the user and/or the surrounding environment. Additionally, [=Immersive sessions=] MUST be given <dfn>exclusive access</dfn> to the [=immersive XR device=], meaning that while the [=immersive session=] is {{XRVisibilityState/"visible"}} the HTML document is not shown on the [=immersive XR device=]'s display, nor does content from any other source have exclusive access. [=Exclusive access=] does not prevent the user agent from overlaying its own UI, however this UI SHOULD be minimal.

Note: Future specifications or modules may expand the definition of [=immersive session=] include additional session modes.

Note: Examples of ways [=exclusive access=] may be presented include stereo content displayed on a virtual reality headset.

Note: As an example of overlaid UI, the user-agent or operating system in an [=immersive session=] may show notifications over the rendered content.

Note: While the HTML document is not shown on the [=immersive XR device=]'s display during an [=immersive session=], it may still be shown on a separate display, e.g. when the user is entering the [=immersive session=] from a 2d browser on their computer tethered to their  [=immersive XR device=].

Feature Dependencies {#feature-dependencies}
--------------------

Some features of an {{XRSession}} may not be universally available for a number of reasons, among which is the fact not all XR devices can support the full set of features. Another consideration is that some features expose [=sensitive information=] which may require a clear signal of [=user intent=] before functioning.

Since it is a poor user experience to initialize the underlying XR platform and create an {{XRSession}} only to immediately notify the user that the applications cannot function correctly, developers can indicate <dfn>required features</dfn> by passing an {{XRSessionInit}} dictionary to {{XRSystem/requestSession()}}. This will block the creation of the {{XRSession}} if any of the [=required features=] are unavailable due to device limitations or in the absence of a clear signal of [=user intent=] to expose [=sensitive information=] related to the feature.

Additionally, developers are encouraged to design experiences which progressively enhance their functionality when run one more capable devices. <dfn>Optional features</dfn> which the experience does not require but will take advantage of when available must also be indicated in an {{XRSessionInit}} dictionary to ensure that [=user intent=] can be determined before enabling the feature if necessary.

<pre class="idl">
dictionary XRSessionInit {
  sequence&lt;any&gt; requiredFeatures;
  sequence&lt;any&gt; optionalFeatures;
};
</pre>

The <dfn dict-member for="XRSessionInit">requiredFeatures</dfn> array contains any [=Required features=] for the experience. If any value in the list is not a recognized [=feature descriptor=] the {{XRSession}} will not be created. If any feature listed in the {{XRSessionInit/requiredFeatures}} array is not supported by the [=XRSession/XR device=] or, if necessary, has not received a clear signal of [=user intent=] the {{XRSession}} will not be created.

The <dfn dict-member for="XRSessionInit">optionalFeatures</dfn> array contains any [=Optional features=] for the experience. If any value in the list is not a recognized [=feature descriptor=] it will be ignored. Features listed in the {{XRSessionInit/optionalFeatures}} array will be enabled if supported by the [=XRSession/XR device=] and, if necessary, given a clear signal of [=user intent=], but will not block creation of the {{XRSession}} if absent.

Values given in the feature lists are considered a valid <dfn>feature descriptor</dfn> if the value is one of the following:

 - Any {{XRReferenceSpaceType}} enum value

Future iterations of this specification and additional modules may expand the list of accepted [=feature descriptors=].

Note: Features are accepted as an array of {{any}} values to ensure forwards compatibility. It allows unrecognized optional values to be properly ignored as new [=feature descriptor=] types are added.

Note: Features that can be stringified via <a abstract-op>ToString</a>() will be converted.

Depending on the {{XRSessionMode}} requested, certain [=feature descriptors=] are added to the {{XRSessionInit/requiredFeatures}} or {{XRSessionInit/optionalFeatures}} lists by default. The following table describes the <dfn>default features</dfn> associated with each session type and feature list:

<table class="tg">
  <thead>
    <tr>
      <th>Feature</th>
      <th>Sessions</th>
      <th>List</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>{{XRReferenceSpaceType/"viewer"}}</td>
      <td>[=Inline sessions=] and [=immersive sessions=]</td>
      <td>{{XRSessionInit/requiredFeatures}}</td>
    </tr>
    <tr>
      <td>{{XRReferenceSpaceType/"local"}}</td>
      <td>[=Immersive sessions=]</td>
      <td>{{XRSessionInit/requiredFeatures}}</td>
    </tr>
  </tbody>
</table>

The combined list of [=feature descriptors=] given by the {{XRSessionInit/requiredFeatures}} and {{XRSessionInit/optionalFeatures}} are collectively considered the <dfn>requested features</dfn> for an {{XRSession}}.

Some [=feature descriptors=], when present in the [=requested features=] list, are subject to [[#permissions-policy|permissions policy]] and/or requirements that [=user intent=] to use the feature is well understood, via either [=explicit consent=] or [=implicit consent=]. The following table describes the <dfn>feature requirements</dfn> that must be satisfied prior to being enabled:

<table class="tg">
  <thead>
    <tr>
      <th>Feature</th>
      <th>[=Permissions Policy=] Required</th>
      <th>Consent Required</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>{{XRReferenceSpaceType/"local"}}</td>
      <td>"xr-spatial-tracking"</td>
      <td>[=Inline sessions=] require consent</td>
    </tr>
    <tr>
      <td>{{XRReferenceSpaceType/"local-floor"}}</td>
      <td>"xr-spatial-tracking"</td>
      <td>Always requires consent</td>
    </tr>
    <tr>
      <td>{{XRReferenceSpaceType/"bounded-floor"}}</td>
      <td>"xr-spatial-tracking"</td>
      <td>Always requires consent</td>
    </tr>
    <tr>
      <td>{{XRReferenceSpaceType/"unbounded"}}</td>
      <td>"xr-spatial-tracking"</td>
      <td>Always requires consent</td>
    </tr>
  </tbody>
</table>

Note: {{XRReferenceSpaceType/"local"}} is always included in the [=requested features=] of [=immersive sessions=] as a [=default feature=], and as such [=immersive sessions=] always need to obtain [=explicit consent=] or [=implicit consent=].

[=Requested features=] can only be enabled for a session if the [=XRSession/XR device=] is <dfn>capable of supporting</dfn> the feature, which means that the feature is known to be supported by the [=XRSession/XR device=] in some configurations, even if the current configuration has not yet been verified as supporting the feature. The user agent MAY apply more rigorous constraints if desired in order to yield a more consistent user experience.

Note: For example, several VR devices support either configuring a safe boundary for the user to move around within or skipping boundary configuration and operating in a mode where the user is expected to stand in place. Such a device can be considered to be [=capable of supporting=] {{"bounded-floor"}} {{XRReferenceSpace}}s even if they are currently not configured with safety boundaries, because it's expected that the user could configure the device appropriately if the experience required it. This is to allow user agents to avoid fully initializing the [=XRSession/XR device=] or waiting for the user's environment to be recognized prior to [=resolve the requested features|resolving the requested features=] if desired. If, however, the user agent knows that the boundary state at the time the session is requested without additional initialization it may choose to reject the {{"bounded-floor"}} feature if the safety boundary not already configured.

Session {#session}
=======

XRSession {#xrsession-interface}
---------

Any interaction with XR hardware is done via an {{XRSession}} object, which can only be retrieved by calling {{XRSystem/requestSession()}} on the {{XRSystem}} object. Once a session has been successfully acquired, it can be used to {{XRFrame/getViewerPose()|poll the viewer pose}}, query information about the user's environment, and present imagery to the user.

The user agent, when possible, <dfn>SHOULD NOT initialize device tracking</dfn> or rendering capabilities until an {{XRSession}} has been acquired. This is to prevent unwanted side effects of engaging the XR systems when they're not actively being used, such as increased battery usage or related utility applications from appearing when first navigating to a page that only wants to test for the presence of XR hardware in order to advertise XR features. Not all XR platforms offer ways to detect the hardware's presence without initializing tracking, however, so this is only a strong recommendation.

<pre class="idl">
enum XRVisibilityState {
  "visible",
  "visible-blurred",
  "hidden",
};

[SecureContext, Exposed=Window] interface XRSession : EventTarget {
  // Attributes
  readonly attribute XRVisibilityState visibilityState;
  [SameObject] readonly attribute XRRenderState renderState;
  [SameObject] readonly attribute XRInputSourceArray inputSources;

  // Methods
  undefined updateRenderState(optional XRRenderStateInit state = {});
  [NewObject] Promise&lt;XRReferenceSpace&gt; requestReferenceSpace(XRReferenceSpaceType type);

  unsigned long requestAnimationFrame(XRFrameRequestCallback callback);
  undefined cancelAnimationFrame(unsigned long handle);

  Promise&lt;undefined&gt; end();

  // Events
  attribute EventHandler onend;
  attribute EventHandler oninputsourceschange;
  attribute EventHandler onselect;
  attribute EventHandler onselectstart;
  attribute EventHandler onselectend;
  attribute EventHandler onsqueeze;
  attribute EventHandler onsqueezestart;
  attribute EventHandler onsqueezeend;
  attribute EventHandler onvisibilitychange;
};
</pre>

Each {{XRSession}} has a <dfn for="XRSession">mode</dfn>, which is one of the values of {{XRSessionMode}}.

Each {{XRSession}} has an <dfn for=XRSession>animation frame</dfn>, which is an {{XRFrame}} initialized with [=XRFrame/active=] set to `false`, [=XRFrame/animationFrame=] set to `true`, and {{XRFrame/session}} set to the {{XRSession}}.

<div class="algorithm" data-algorithm="initialize-session">

To <dfn>initialize the session</dfn>, given |session|, |mode|, and |device|, the user agent MUST run the following steps:
  1. Set |session|'s [=XRSession/mode=] to |mode|.
  1. Set |session|'s [=XRSession/XR device=] to |device|.
  1. [=Initialize the render state=].
  1. If no other features of the user agent have done so already, perform the necessary platform-specific steps to initialize the device's tracking and rendering capabilities, including showing any necessary instructions to the user.

Note: Some devices require additional user instructions for activation. For example, going into immersive mode on a phone-based headset device requires inserting the phone into the headset, and doing so on a desktop browser connected to an external headset requires wearing the headset. It is the responsibility of the user agent &mdash; not the author &mdash; to ensure any such instructions are shown.

</div>

A number of different circumstances may <dfn>shut down the session</dfn>, which is permanent and irreversible. Once a session has been shut down the only way to access the [=XRSession/XR device=]'s tracking or rendering capabilities again is to request a new session. Each {{XRSession}} has an <dfn>ended</dfn> boolean, initially set to `false`, that indicates if it has been shut down.

<div class="algorithm" data-algorithm="shut-down-session">

When an {{XRSession}} |session| is shut down the following steps are run:

  1. Set |session|'s [=ended=] value to `true`.
  1. If the [=active immersive session=] is equal to |session|, set the [=active immersive session=] to `null`.
  1. Remove |session| from the [=list of inline sessions=].
  1. [=Reject=] any outstanding promises returned by |session| with an {{InvalidStateError}}, except for any promises returned by {{XRSession/end()}}.
  1. If no other features of the user agent are actively using them, perform the necessary platform-specific steps to shut down the device's tracking and rendering capabilities. This MUST include:
    - Releasing [=exclusive access=] to the [=XRSession/XR device=] if |session| is an [=immersive session=].
    - Deallocating any graphics resources acquired by |session| for presentation to the [=XRSession/XR device=].
    - Putting the [=XRSession/XR device=] in a state such that a different source may be able to initiate a session with the same device if |session| is an [=immersive session=].
  1. [=Queue a task=] that fires an {{XRSessionEvent}} named {{end!!event}} on |session|.

</div>

<div class="algorithm" data-algorithm="end-session">

The <dfn method for="XRSession">end()</dfn> method provides a way to manually shut down a session. When invoked, it MUST run the following steps:

  1. Let |promise| be [=a new Promise=] in the [=relevant realm=] of this {{XRSession}}.
  1. [=Shut down the session|Shut down=] [=this=].
  1. [=Queue a task=] to perform the following steps:
    1. Wait until any platform-specific steps related to shutting down the session have completed.
    1. [=/Resolve=] |promise|.
  1. Return |promise|.

</div>

Each {{XRSession}} has an <dfn>active render state</dfn> which is a new {{XRRenderState}}, and a <dfn>pending render state</dfn>, which is an {{XRRenderState}} which is initially `null`.

The <dfn attribute for="XRSession">renderState</dfn> attribute returns the {{XRSession}}'s [=active render state=].

Each {{XRSession}} has a <dfn>minimum inline field of view</dfn> and a <dfn>maximum inline field of view</dfn>, defined in radians. The values MUST be determined by the user agent and MUST fall in the range of `0` to `PI`.

Each {{XRSession}} has a <dfn>minimum near clip plane</dfn> and a <dfn>maximum far clip plane</dfn>, defined in meters. The values MUST be determined by the user agent and MUST be non-negative. The [=minimum near clip plane=] SHOULD be less than `0.1`. The [=maximum far clip plane=] SHOULD be greater than `1000.0` (and MAY be infinite).

<div class="algorithm" data-algorithm="update-layers-state">

When the user agent will <dfn>update the pending layers state</dfn> with {{XRSession}} <var ignore>session</var> and {{XRRenderStateInit}} |newState|, it must run the following steps:
  1. If |newState|'s {{XRRenderStateInit/layers}}'s value is not `null`, throw a {{NotSupportedError}}.

NOTE: The <a href="https://immersive-web.github.io/layers">WebXR layers module</a> will introduce new semantics for this algorithm.

</div>

<div class="algorithm" data-algorithm="update-render-state">

The <dfn method for="XRSession">updateRenderState(|newState|)</dfn> method queues an update to the [=active render state=] to be applied on the next frame. Unset fields of the {{XRRenderStateInit}} |newState| passed to this method will not be changed.

When this method is invoked, the user agent MUST run the following steps:
  1. Let |session| be [=this=].
  1. If |session|'s [=ended=] value is `true`, throw an {{InvalidStateError}} and abort these steps.
  1. If |newState|'s {{XRRenderStateInit/baseLayer}} was created with an {{XRSession}} other than |session|, throw an {{InvalidStateError}} and abort these steps.
  1. If |newState|'s {{XRRenderStateInit/inlineVerticalFieldOfView}} is set and |session| is an [=immersive session=], throw an {{InvalidStateError}} and abort these steps.
  1. If none of |newState|'s {{XRRenderStateInit/depthNear}}, {{XRRenderStateInit/depthFar}}, {{XRRenderStateInit/inlineVerticalFieldOfView}}, {{XRRenderStateInit/baseLayer}}, {{XRRenderStateInit/layers}} are set, abort these steps.
  1. Run [=update the pending layers state=] with |session| and |newState|.
  1. Let |activeState| be |session|'s [=active render state=].
  1. If |session|'s [=pending render state=] is `null`, set it to a copy of |activeState|.
  1. If |newState|'s {{XRRenderStateInit/depthNear}} value is set, set |session|'s [=pending render state=]'s {{XRRenderState/depthNear}} to |newState|'s {{XRRenderStateInit/depthNear}}.
  1. If |newState|'s {{XRRenderStateInit/depthFar}} value is set, set |session|'s [=pending render state=]'s {{XRRenderState/depthFar}} to |newState|'s {{XRRenderStateInit/depthFar}}.
  1. If |newState|'s {{XRRenderStateInit/inlineVerticalFieldOfView}} is set, set |session|'s [=pending render state=]'s {{XRRenderState/inlineVerticalFieldOfView}} to |newState|'s {{XRRenderStateInit/inlineVerticalFieldOfView}}.
  1. If |newState|'s {{XRRenderStateInit/baseLayer}} is set, set |session|'s [=pending render state=]'s {{XRRenderState/baseLayer}} to |newState|'s {{XRRenderStateInit/baseLayer}}.

</div>

<div class="algorithm" data-algorithm="apply-pending-render-state">

When requested, the {{XRSession}} |session| MUST <dfn>apply the pending render state</dfn> by running the following steps:

  1. Let |activeState| be |session|'s [=active render state=].
  1. Let |newState| be |session|'s [=pending render state=].
  1. Set |session|'s [=pending render state=] to `null`.
  1. Let |oldBaseLayer| be |activeState|'s {{XRRenderState/baseLayer}}.
  1. Let |oldLayers| be |activeState|'s {{XRRenderState/layers}}.
  1. [=Queue a task=] to perform the following steps:
    1. Set |activeState| to |newState|.
    1. If |oldBaseLayer| is not equal to |activeState|'s {{XRRenderState/baseLayer}}, |oldLayers| is not equal to |activeState|'s {{XRRenderState/layers}}, or the dimensions of any of the layers have changed, [=update the viewports=] for |session|.
    1. If |activeState|'s {{XRRenderState/inlineVerticalFieldOfView}} is less than |session|'s [=minimum inline field of view=] set |activeState|'s {{XRRenderState/inlineVerticalFieldOfView}} to |session|'s [=minimum inline field of view=].
    1. If |activeState|'s {{XRRenderState/inlineVerticalFieldOfView}} is greater than |session|'s [=maximum inline field of view=] set |activeState|'s {{XRRenderState/inlineVerticalFieldOfView}} to |session|'s [=maximum inline field of view=].
    1. If |activeState|'s {{XRRenderState/depthNear}} is less than |session|'s [=minimum near clip plane=] set |activeState|'s {{XRRenderState/depthNear}} to |session|'s [=minimum near clip plane=].
    1. If |activeState|'s {{XRRenderState/depthFar}} is greater than |session|'s [=maximum far clip plane=] set |activeState|'s {{XRRenderState/depthFar}} to |session|'s [=maximum far clip plane=].
    1. Let |baseLayer| be |activeState|'s {{XRRenderState/baseLayer}}.
    1. Set |activeState|'s [=XRRenderState/composition disabled=] and [=XRRenderState/output canvas=] as follows:
        <dl class="switch">
          : If |session|'s [=XRSession/mode=] is {{XRSessionMode/"inline"}} and |baseLayer| is an instance of an {{XRWebGLLayer}} with [=XRWebGLLayer/composition disabled=] set to `true`:
          :: Set |activeState|'s [=XRRenderState/composition disabled=] boolean to `true`.
          :: Set |activeState|'s [=XRRenderState/output canvas=] to |baseLayer|'s [=XRWebGLLayer/context=]'s {{WebGLRenderingContext|canvas}}.

          : Otherwise:
          :: Set |activeState|'s [=XRRenderState/composition disabled=] boolean to `false`.
          :: Set |activeState|'s [=XRRenderState/output canvas=] to `null`.

        </dl>

</div>

<div class="algorithm" data-algorithm="request-reference-space">
The <dfn method for="XRSession">requestReferenceSpace(|type|)</dfn> method constructs a new {{XRReferenceSpace}} of a given |type|, if possible.

When this method is invoked, the user agent MUST run the following steps:

  1. Let |promise| be [=a new Promise=] in the [=relevant realm=] of this {{XRSession}}.
  1. Run the following steps [=in parallel=]:
    1. If the result of running [=reference space is supported=] for |type| and |session| is `false`, [=queue a task=] to [=reject=] |promise| with a {{NotSupportedError}} and abort these steps.
    1. Set up any platform resources required to track reference spaces of type |type|.
        <div class=note>User agents need not wait for tracking to be established for such reference spaces to resolve {{XRSession/requestReferenceSpace()}}. It is okay for {{XRFrame/getViewerPose()}} to return `null` when the session is initially attempting to establish tracking, and content can use this time to show a splash screen or something else. Note that if |type| is {{XRReferenceSpaceType/"bounded-floor"}}, and the bounds have not yet been established, user agents MAY set the bounds to a small initial area and use a {{reset}} event when bounds are established.</div>
    1. [=Queue a task=] to run the following steps:
      1. [=Create a reference space=], |referenceSpace|, with |type| and |session|.
      1. [=/Resolve=] |promise| with |referenceSpace|.
  1. Return |promise|.

</div>

Each {{XRSession}} has a <dfn>list of active XR input sources</dfn> (a [=/list=] of {{XRInputSource}}) which MUST be initially an empty [=/list=].

Each {{XRSession}} has an <dfn for="XRSession">XR device</dfn>, which is an [=/XR device=] set at initialization.

The <dfn attribute for="XRSession">inputSources</dfn> attribute returns the {{XRSession}}'s [=list of active XR input sources=].

The user agent MUST monitor any [=XR input source=]s associated with the [=XRSession/XR device=], including detecting when [=XR input source=]s are added, removed, or changed.

Each {{XRSession}} has a <dfn for=XRSession>promise resolved</dfn> flag, initially `false`.

NOTE: The purpose of this flag is to ensure that the [=XRSession/add input source=], [=XRSession/remove input source=], and [=XRSession/change input source=] algorithms do not run until the user code actually has had a chance to attach event listeners. Implementations may not need this flag if they simply choose to start listening for input source changes after the session resolves.

<div class="algorithm" data-algorithm="on-input-source-added">

When <dfn for="XRSession" lt="add input source">new [=XR input source=]s become available</dfn> for {{XRSession}} |session|, the user agent MUST run the following steps:

  1. If |session|'s [=XRSession/promise resolved=] flag is not set, abort these steps.
  1. Let |added| be a new [=/list=].
  1. For each new [=XR input source=]:
    1. Let |inputSource| be a [=new=] {{XRInputSource}} in the [=relevant realm=] of this {{XRSession}}.
    1. Add |inputSource| to |added|.
  1. [=Queue a task=] to perform the following steps:
    1. [=list/Extend=] |session|'s [=list of active XR input sources=] with |added|.
    1. Fire an {{XRInputSourcesChangeEvent}} named {{inputsourceschange!!event}} on |session| with {{XRInputSourcesChangeEvent/added}} set to |added|.

</div>

<div class="algorithm" data-algorithm="on-input-source-removed">

When any previously added <dfn for="XRSession" lt="remove input source">[=XR input source=]s are no longer available</dfn> for {{XRSession}} |session|, the user agent MUST run the following steps:

  1. If |session|'s [=XRSession/promise resolved=] flag is not set, abort these steps.
  1. Let |removed| be a new [=/list=].
  1. For each [=XR input source=] that is no longer available:
    1. Let |inputSource| be the {{XRInputSource}} in |session|'s [=list of active XR input sources=] associated with the [=XR input source=].
    1. Add |inputSource| to |removed|.
  1. [=Queue a task=] to perform the following steps:
    1. [=list/Remove=] each {{XRInputSource}} in |removed| from |session|'s [=list of active XR input sources=].
    1. Fire an {{XRInputSourcesChangeEvent}} named {{inputsourceschange!!event}} on |session| with {{XRInputSourcesChangeEvent/removed}} set to |removed|.

Note: The user agent MAY fire this event when an input source temporarily loses both position and orientation tracking. It is recommended that this only be done for physical handheld controller input sources. It is not recommended that this event be fired when this happens for tracked hand input sources, because this will happen often, nor is it recommended when this happens for tracker object input sources, since this makes it harder for the application to maintain a notion of identity.

</div>

<div class="algorithm" data-algorithm="on-input-source-change">

When the <dfn for="XRSession" export lt="change input source">{{XRInputSource/handedness}}, {{XRInputSource/targetRayMode}}, {{XRInputSource/profiles}}, or presence of a {{XRInputSource/gripSpace}} for any [=XR input source=]s change</dfn> for {{XRSession}} |session|, the user agent MUST run the following steps:

  1. If |session|'s [=XRSession/promise resolved=] flag is not set, abort these steps.
  1. Let |added| be a new [=/list=].
  1. Let |removed| be a new [=/list=].
  1. For each changed [=XR input source=]:
    1. Let |oldInputSource| be the {{XRInputSource}} in |session|'s [=list of active XR input sources=] previously associated with the [=XR input source=].
    1. Let |newInputSource| be a [=new=] {{XRInputSource}} in the [=relevant realm=] of |session|.
    1. Add |oldInputSource| to |removed|.
    1. Add |newInputSource| to |added|.
  1. [=Queue a task=] to perform the following steps:
    1. [=list/Remove=] each {{XRInputSource}} in |removed| from |session|'s [=list of active XR input sources=].
    1. [=list/Extend=] |session|'s [=list of active XR input sources=] with |added|.
    1. Fire an {{XRInputSourcesChangeEvent}} named {{inputsourceschange!!event}} on |session| with{{XRInputSourcesChangeEvent/added}} set to |added| and {{XRInputSourcesChangeEvent/removed}} set to |removed|.

</div>

Each {{XRSession}} has a <dfn for="XRSession">visibility state</dfn> value, which is an enum. For [=inline sessions=] the [=XRSession/visibility state=] MUST mirror the {{Document}}'s [=visibilityState=]. For [=immersive sessions=] the [=XRSession/visibility state=] MUST be set to whichever of the following values best matches the state of session.

  - A state of <dfn enum-value for="XRVisibilityState">visible</dfn> indicates that imagery rendered by the {{XRSession}} can be seen by the user and {{XRSession/requestAnimationFrame()}} callbacks are processed at the [=XRSession/XR device=]'s native refresh rate. Input is processed by the {{XRSession}} normally.

  - A state of <dfn enum-value for="XRVisibilityState">visible-blurred</dfn> indicates that imagery rendered by the {{XRSession}} may be seen by the user, but is not the primary focus. {{XRSession/requestAnimationFrame()}} callbacks MAY be [=throttling|throttled=]. Input is not processed by the {{XRSession}}.

  - A state of <dfn enum-value for="XRVisibilityState">hidden</dfn> indicates that imagery rendered by the {{XRSession}} cannot be seen by the user. {{XRSession/requestAnimationFrame()}} callbacks will not be processed until the [=visibility state=] changes. Input is not processed by the {{XRSession}}.

The <dfn attribute for="XRSession">visibilityState</dfn> attribute returns the {{XRSession}}'s [=visibility state=]. The <dfn attribute for="XRSession">onvisibilitychange</dfn> attribute is an [=Event handler IDL attribute=] for the {{visibilitychange}} event type.

The [=visibility state=] MAY be changed by the user agent at any time other than during the processing of an [=XR animation frame=], and the user agent SHOULD monitor the XR platform when possible to observe when session visibility has been affected external to the user agent and update the [=visibility state=] accordingly.

Note: The {{XRSession}}'s [=visibility state=] does not necessarily imply the visibility of the HTML document. Depending on the system configuration the page may continue to be visible while an [=immersive session=] is active. (For example, a headset connected to a PC may continue to display the page on the monitor while the headset is viewing content from an [=immersive session=].) Developers should continue to rely on the [Page Visibility API](https://w3c.github.io/page-visibility/) to determine page visibility.

Note: The {{XRSession}}'s [=visibility state=] does not affect or restrict mouse behavior on tethered sessions where 2D content is still visible while an [=immersive session=] is active. Content should consider using the [[!pointerlock]] API if it wishes to have stronger control over mouse behavior.

Each {{XRSession}} has a <dfn for="XRSession">viewer reference space</dfn>, which is an {{XRReferenceSpace}} of type {{XRReferenceSpaceType/"viewer"}} with an [=identity transform=] [=XRSpace/origin offset=].

Each {{XRSession}} has a <dfn for="XRSession">list of views</dfn>, which is a [=/list=] of [=view=]s corresponding to the views provided by the [=XRSession/XR device=]. If the {{XRSession}}'s {{XRSession/renderState}}'s [=XRRenderState/composition disabled=] boolean is set to `true` the [=list of views=] MUST contain a single [=view=]. The [=XRSession/list of views=] is immutable during the {{XRSession}} and MUST contain any [=views=] that may be surfaced during the session, including [=secondary views=] that may not initially be [=view/active=].

The <dfn attribute for="XRSession">onend</dfn> attribute is an [=Event handler IDL attribute=] for the {{end}} event type.

The <dfn attribute for="XRSession">oninputsourceschange</dfn> attribute is an [=Event handler IDL attribute=] for the {{inputsourceschange}} event type.

The <dfn attribute for="XRSession">onselectstart</dfn> attribute is an [=Event handler IDL attribute=] for the {{selectstart}} event type.

The <dfn attribute for="XRSession">onselectend</dfn> attribute is an [=Event handler IDL attribute=] for the {{selectend}} event type.

The <dfn attribute for="XRSession">onselect</dfn> attribute is an [=Event handler IDL attribute=] for the {{XRSession/select}} event type.


The <dfn attribute for="XRSession">onsqueezestart</dfn> attribute is an [=Event handler IDL attribute=] for the {{squeezestart}} event type.

The <dfn attribute for="XRSession">onsqueezeend</dfn> attribute is an [=Event handler IDL attribute=] for the {{squeezeend}} event type.

The <dfn attribute for="XRSession">onsqueeze</dfn> attribute is an [=Event handler IDL attribute=] for the {{XRSession/squeeze}} event type.

XRRenderState {#xrrenderstate-interface}
-------------

An {{XRRenderState}} represents a set of configurable values which affect how an {{XRSession}}'s output is composited. The [=active render state=] for a given {{XRSession}} can only change between frame boundaries, and updates can be queued up via {{XRSession/updateRenderState()}}.

<pre class="idl">
dictionary XRRenderStateInit {
  double depthNear;
  double depthFar;
  double inlineVerticalFieldOfView;
  XRWebGLLayer? baseLayer;
  sequence&lt;XRLayer&gt;? layers;
};

[SecureContext, Exposed=Window] interface XRRenderState {
  readonly attribute double depthNear;
  readonly attribute double depthFar;
  readonly attribute double? inlineVerticalFieldOfView;
  readonly attribute XRWebGLLayer? baseLayer;
};
</pre>

Each {{XRRenderState}} has a <dfn for="XRRenderState">output canvas</dfn>, which is an {{HTMLCanvasElement}} initially set to `null`. The [=XRRenderState/output canvas=] is the DOM element that will display any content rendered for an {{XRSessionMode/"inline"}} {{XRSession}}.

Each {{XRRenderState}} also has <dfn for="XRRenderState">composition disabled</dfn> boolean, which is initially `false`. The {{XRRenderState}} is considered to be have [=XRRenderState/composition disabled=] if rendering commands performed for an {{XRSessionMode/"inline"}} {{XRSession}} are executed in such a way that they are directly displayed into [=XRRenderState/output canvas=], rather than first being processed by the [=XR Compositor=].

Note: At this point the {{XRRenderState}} will only have an [=XRRenderState/output canvas=] if it has [=XRRenderState/composition disabled=], but future versions of the specification are likely to introduce methods for setting [=XRRenderState/output canvas=]' that support more advanced uses like mirroring and layer compositing that will require composition.

<div class="algorithm" data-algorithm="initialize-renderstate">

When an {{XRRenderState}} object is created for an {{XRSession}} |session|, the user agent MUST <dfn>initialize the render state</dfn> by running the following steps:

  1. Let |state| be a [=new=] {{XRRenderState}} object in the [=relevant realm=] of |session|.
  1. Initialize |state|'s {{XRRenderState/depthNear}} to `0.1`.
  1. Initialize |state|'s {{XRRenderState/depthFar}} to `1000.0`.
  1. Initialize |state|'s {{XRRenderState/inlineVerticalFieldOfView}} as follows:
    <dl class="switch">
      : If |session| is an [=inline session=]:
      :: Initialize |state|'s {{XRRenderState/inlineVerticalFieldOfView}} to `PI * 0.5`.

      : Otherwise:
      :: Initialize |state|'s {{XRRenderState/inlineVerticalFieldOfView}} to `null`.

    </dl>
  1. Initialize |state|'s {{XRRenderState/baseLayer}} to `null`.

</div>

The <dfn attribute for="XRRenderState">depthNear</dfn> attribute defines the distance, in meters, of the near clip plane from the [=viewer=]. The <dfn attribute for="XRRenderState">depthFar</dfn> attribute defines the distance, in meters, of the far clip plane from the [=viewer=].

{{XRRenderState/depthNear}} and {{XRRenderState/depthFar}} are used in the computation of the {{XRView/projectionMatrix}} of {{XRView}}s. When the {{XRView/projectionMatrix}} is used during rendering, only geometry with a distance to the [=viewer=] that falls between {{XRRenderState/depthNear}} and {{XRRenderState/depthFar}} will be drawn. They also determine how the values of an {{XRWebGLLayer}} depth buffer are interpreted. {{XRRenderState/depthNear}} MAY be greater than {{XRRenderState/depthFar}}.

Note: Typically when constructing a perspective projection matrix for rendering the developer specifies the viewing frustum and the near and far clip planes. When displaying to an [=immersive XR device=] the correct viewing frustum is determined by some combination of the optics, displays, and cameras being used. The near and far clip planes, however, may be modified by the application since the appropriate values depend on the type of content being rendered.

The <dfn attribute for="XRRenderState">inlineVerticalFieldOfView</dfn> attribute defines the default vertical field of view in radians used when computing projection matrices for {{XRSessionMode/"inline"}} {{XRSession}}s. The projection matrix calculation also takes into account the aspect ratio of the [=XRRenderState/output canvas=]. This value MUST be `null` for [=immersive sessions=].

The <dfn attribute for="XRRenderState">baseLayer</dfn> attribute defines an {{XRWebGLLayer}} which the [=XR compositor=] will obtain images from.

Animation Frames {#animation-frames}
----------------

The primary way an {{XRSession}} provides information about the tracking state of the [=XRSession/XR device=] is via callbacks scheduled by calling {{requestAnimationFrame()}} on the {{XRSession}} instance.

<pre class="idl">
callback XRFrameRequestCallback = undefined (DOMHighResTimeStamp time, XRFrame frame);
</pre>

Each {{XRFrameRequestCallback}} object has a <dfn for="XRFrameRequestCallback">cancelled</dfn> boolean initially set to `false`.

Each {{XRSession}} has a <dfn>list of animation frame callbacks</dfn>, which is initially empty, a  <dfn>list of currently running animation frame callbacks</dfn>, which is also initially empty, and an <dfn>animation frame callback identifier</dfn>, which is a number which is initially zero.

<div class="algorithm" data-algorithm="request-animation-frame">

The <dfn method for="XRSession">requestAnimationFrame(|callback|)</dfn> method queues up |callback| for being run the next time the user agent wishes to run an animation frame for the device.

When this method is invoked, the user agent MUST run the following steps:

  1. Let |session| be the [=this=].
  1. Increment |session|'s [=animation frame callback identifier=] by one.
  1. Append |callback| to |session|'s [=list of animation frame callbacks=], associated with |session|'s [=animation frame callback identifier=]’s current value.
  1. Return |session|'s [=animation frame callback identifier=]’s current value.

</div>

<div class="algorithm" data-algorithm="cancel-animation-frame">

The <dfn method for="XRSession">cancelAnimationFrame(|handle|)</dfn> method cancels an existing animation frame callback given its [=animation frame callback identifier=] |handle|.

When this method is invoked, the user agent MUST run the following steps:

  1. Let |session| be the [=this=].
  1. Find the entry in |session|'s [=list of animation frame callbacks=] or |session|'s [=list of currently running animation frame callbacks=] that is associated with the value |handle|.
  1. If there is such an entry, set its [=cancelled=] boolean to `true` and remove it from |session|'s [=list of animation frame callbacks=].

</div>

<div class="algorithm" data-algorithm="check-layers-state">
To <dfn>check the layers state</dfn> with {{XRSession/renderState}} |state|, the user agent MUST run the following steps:
  1. If |state|'s {{XRRenderState/baseLayer}} is `null`, return `false`.
  1. return `true`.

NOTE: The <a href="https://immersive-web.github.io/layers">WebXR layers module</a> will introduce new semantics for this algorithm.

</div>

<div class="algorithm" data-algorithm="should-be-rendered">
To determine if a frame <dfn>should be rendered</dfn> for {{XRSession}} |session|, the user agent MUST run the following steps:
  1. If [=check the layers state=] with |session|'s {{XRSession/renderState}} is `false`, return `false`.
  1. If |session|'s [=XRSession/mode=] is {{XRSessionMode/"inline"}} and |session|'s {{XRSession/renderState}}'s [=XRRenderState/output canvas=] is `null`, return `false`.
  1. return `true`.

</div>

<div class="algorithm" data-algorithm="run-animation-frames">

When an {{XRSession}} |session| receives updated [=viewer=] state for timestamp |frameTime| from the [=XRSession/XR device=], it runs an <dfn>XR animation frame</dfn>, which MUST run the following steps regardless of if the [=list of animation frame callbacks=] is empty or not:

  1. [=Queue a task=] to perform the following steps:
    1. Let |now| be the [=current high resolution time=].
    1. Let |frame| be |session|'s [=XRSession/animation frame=].
    1. Set |frame|'s [=XRFrame/time=] to |frameTime|.
    1. For each |view| in [=XRSession/list of views=], set |view|'s [=view/viewport modifiable=] flag to true.
    1. If the [=view/active=] flag of any [=view=] in the [=XRSession/list of views=] has changed since the last [=XR animation frame=], [=update the viewports=].
    1. If the frame [=should be rendered=] for |session|:
        1. Set |session|'s [=list of currently running animation frame callbacks=] to be |session|'s [=list of animation frame callbacks=].
        1. Set |session|'s [=list of animation frame callbacks=] to the empty list.
        1. Set |frame|'s [=XRFrame/active=] boolean to `true`.
        1. [=XRFrame/Apply frame updates=] for |frame|.
        1. For each |entry| in |session|'s [=list of currently running animation frame callbacks=], in order:
          1. If the |entry|'s [=cancelled=] boolean is `true`, continue to the next entry.
          1. [=Invoke the Web IDL callback function=] for |entry|, passing |now| and |frame| as the  arguments
          1. If an exception is thrown, [=report the exception=].
        1. Set |session|'s [=list of currently running animation frame callbacks=] to the empty [=/list=].
        1. Set |frame|'s [=XRFrame/active=] boolean to `false`.
    1. If |session|'s [=pending render state=] is not `null`, [=apply the pending render state=].

</div>

The behavior of the {{Window}} interface's {{Window/requestAnimationFrame()}} method is not changed by the presence of any active {{XRSession}}, nor does calling {{requestAnimationFrame()}} on any {{XRSession}} interact with {{Window}}'s {{Window/requestAnimationFrame()}} in any way. An [=active immersive session=] MAY affect the [=rendering opportunity=] of a [=browsing context=] if it causes the page to be obscured. If the 2D browser view is visible during an [=active immersive session=] (i.e., when the sesson is running on a tethered headset), the timing of callbacks run with {{Window}}'s {{Window/requestAnimationFrame()}} and {{Window/requestIdleCallback()}} MAY NOT coincide with that of the session's {{XRSession/requestAnimationFrame()}} and should not be relied upon by the user for rendering XR content.

Note: User agents may wish to display a warning to the developer console if {{XRSession}}'s {{XRSession/requestAnimationFrame()}} is called during callbacks scheduled via {{Window}}'s {{Window/requestAnimationFrame()}}, as these callbacks are not guaranteed to occur if the [=active immersive session=] affects the [=rendering opportunity=] of the [=browsing context=], and may not have the correct timing even if they run.

<div class="example">
If an [=immersive session=] prevents [=rendering opportunity|rendering opportunities=] then callbacks supplied to {{Window}} {{Window/requestAnimationFrame()}} may not be processed while the session is active. This depends on the type of device being used and is most likely to happen depend on mobile or standalone devices where the immersive content completely obscures the HTML document. As such, developers must not rely on {{Window}} {{Window/requestAnimationFrame()}} callbacks to schedule {{XRSession}} {{XRSession/requestAnimationFrame()}} callbacks and visa-versa, even if they share the same rendering logic. Applications that do not follow this guidance may not execute properly on all platforms. A more effective pattern for applications that wish to transition between these two types of animation loops is demonstrated below:

<pre highlight="js">
let xrSession = null;

function onWindowAnimationFrame(time) {
  window.requestAnimationFrame(onWindowAnimationFrame);

  // This may be called while an immersive session is running on some devices,
  // such as a desktop with a tethered headset. To prevent two loops from
  // rendering in parallel, skip drawing in this one until the session ends.
  if (!xrSession) {
    renderFrame(time, null);
  }
}

// The window animation loop can be started immediately upon the page loading.
window.requestAnimationFrame(onWindowAnimationFrame);

function onXRAnimationFrame(time, xrFrame) {
  xrSession.requestAnimationFrame(onXRAnimationFrame);
  renderFrame(time, xrFrame);
}

function renderFrame(time, xrFrame) {
  // Shared rendering logic.
}

// Assumed to be called by a user gesture event elsewhere in code.
async function startXRSession() {
  xrSession = await navigator.xr.requestSession('immersive-vr');
  xrSession.addEventListener('end', onXRSessionEnded);
  // Do necessary session setup here.
  // Begin the session's animation loop.
  xrSession.requestAnimationFrame(onXRAnimationFrame);
}

function onXRSessionEnded() {
  xrSession = null;
}
</pre>

Applications which use {{XRSessionMode/"inline"}} sessions for rendering to the HTML document do not need to take any special steps to coordinate the animation loops, since the user agent will automatically suspend the animation loops of any {{XRSessionMode/"inline"}} sessions while an [=immersive session=] is active.
</div>

The XR Compositor {#compositor}
-----------------

The user agent MUST maintain an <dfn>XR Compositor</dfn> which handles presentation to the [=XRSession/XR device=] and frame timing. The compositor MUST use an independent rendering context whose state is isolated from that of any graphics contexts created by the document. The compositor MUST prevent the page from corrupting the compositor state or reading back content from other pages or applications. The compositor MUST also run in separate thread or processes to decouple performance of the page from the ability to present new imagery to the user at the appropriate framerate. The compositor MAY composite additional device or user agent UI over rendered content, like device menus.

Note: Future extensions to this spec may utilize the compositor to composite multiple layers coming from the same page as well.

Frame Loop {#frame}
==========

XRFrame {#xrframe-interface}
-------

An {{XRFrame}} represents a snapshot of the state of all of the tracked objects for an {{XRSession}}. Applications can acquire an {{XRFrame}} by calling {{XRSession/requestAnimationFrame()}} on an {{XRSession}} with an {{XRFrameRequestCallback}}. When the callback is called it will be passed an {{XRFrame}}. Events which need to communicate tracking state, such as the {{select}} event, will also provide an {{XRFrame}}.

<pre class="idl">
[SecureContext, Exposed=Window] interface XRFrame {
  [SameObject] readonly attribute XRSession session;

  XRViewerPose? getViewerPose(XRReferenceSpace referenceSpace);
  XRPose? getPose(XRSpace space, XRSpace baseSpace);
};
</pre>

Each {{XRFrame}} has an <dfn for="XRFrame">active</dfn> boolean which is initially set to `false`, and an <dfn for="XRFrame">animationFrame</dfn> boolean which is initially set to `false`.

The <dfn attribute for="XRFrame">session</dfn> attribute returns the {{XRSession}} that produced the {{XRFrame}}.

Each {{XRFrame}} represents the state of all tracked objects for a given <dfn for="XRFrame">time</dfn>, and either stores or is able to query concrete information about this state at the [=XRFrame/time=].

<div class="algorithm" data-algorithm="get-viewer-pose">

The <dfn method for="XRFrame">getViewerPose(|referenceSpace|)</dfn> method provides the pose of the [=viewer=] relative to |referenceSpace| as an {{XRViewerPose}}, at the {{XRFrame}}'s [=XRFrame/time=].

When this method is invoked, the user agent MUST run the following steps:

  1. Let |frame| be [=this=].
  1. Let |session| be |frame|'s {{XRFrame/session}} object.
  1. If |frame|'s [=animationFrame=] boolean is `false`, throw an {{InvalidStateError}} and abort these steps.
  1. Let |pose| be a [=new=] {{XRViewerPose}} object in the [=relevant realm=] of |session|.
  1. [=Populate the pose=] of |session|'s [=XRSession/viewer reference space=] in |referenceSpace| at the time represented by |frame| into |pose|, with `force emulation` set to `true`.
  1. If |pose| is `null` return `null`.
  1. Let |xrviews| be an empty [=/list=].
  1. For each [=view/active=] [=view=] |view| in the [=XRSession/list of views=] on {{XRFrame/session}}, perform the following steps:
      1. Let |xrview| be a new {{XRView}} object in the [=relevant realm=] of |session|.
      1. Initialize |xrview|'s [=XRView/underlying view=] to |view|.
      1. Initialize |xrview|'s {{XRView/eye}} to |view|'s [=view/eye=].
      1. Initialize |xrview|'s [=XRView/frame time=] to |frame|'s [=XRFrame/time=].
      1. Initialize |xrview|'s [=XRView/session=] to |session|.
      1. Let |offset| be an [=new=] {{XRRigidTransform}} object equal to the [=view offset=] of |view| in the [=relevant realm=] of |session|.
      1. Set |xrview|'s {{XRView/transform}} property to the result of [=multiply transforms|multiplying=] the {{XRViewerPose}}'s {{XRPose/transform}} by the |offset| transform in the relevant realm of |session|
      1. [=list/Append=] |xrview| to |xrviews|
  1. Set |pose|'s {{XRViewerPose/views}} to |xrviews|
  1. Return |pose|.

</div>

<div class="algorithm" data-algorithm="get-pose">

The <dfn method for="XRFrame">getPose(|space|, |baseSpace|)</dfn> method provides the pose of |space| relative to |baseSpace| as an {{XRPose}}, at the time represented by the {{XRFrame}}.

When this method is invoked, the user agent MUST run the following steps:

  1. Let |frame| be [=this=].
  1. Let |pose| be a [=new=] {{XRPose}} object in the [=relevant realm=] of |frame|.
  1. [=Populate the pose=] of |space| in |baseSpace| at the time represented by |frame| into |pose|.
  1. Return |pose|.

</div>

A <dfn>frame update</dfn> is an algorithm that can be run given an {{XRFrame}}, which is intended to be run each {{XRFrame}}.

Every {{XRSession}} has a <dfn for="XRSession">list of frame updates</dfn>, which is a [=/list=] of [=frame updates=], initially the empty [=/list=].

<div class="algorithm" data-algorithm="apply-frame-updates">

To <dfn for="XRFrame">apply frame updates</dfn> for an {{XRFrame}} |frame|, the user agent MUST run the following steps:

 1. For each |frame update| in |frame|'s {{XRFrame/session}}'s [=XRSession/list of frame updates=], perform the following steps:
    1. Run |frame update| with |frame|.

</div>

NOTE: This spec does not define any [=frame updates=], but other specifications may add some.

Spaces {#spaces}
======

A core feature of the WebXR Device API is the ability to provide spatial tracking. Spaces are the interface that enable applications to reason about how tracked entities are spatially related to the user's physical environment and each other.

XRSpace {#xrspace-interface}
-------

An {{XRSpace}} represents a virtual coordinate system with an origin that corresponds to a physical location. Spatial data that is requested from the API or given to the API is always expressed in relation to a specific {{XRSpace}} at the time of a specific {{XRFrame}}. Numeric values such as pose positions are coordinates in that space relative to its origin. The interface is intentionally opaque.

<pre class="idl">
[SecureContext, Exposed=Window] interface XRSpace : EventTarget {

};
</pre>

Each {{XRSpace}} has a <dfn for="XRSpace">session</dfn> which is set to the {{XRSession}} that created the {{XRSpace}}.

Each {{XRSpace}} has a <dfn for="XRSpace">native origin</dfn> which is a position and orientation in space. The {{XRSpace}}'s [=XRSpace/native origin=] may be updated by the [=XRSession/XR device=]'s underlying tracking system, and different {{XRSpace}}s may define different semantics as to how their [=native origins=] are tracked and updated.

Each {{XRSpace}} has an <dfn for="XRSpace">effective origin</dfn>, which is the basis of the {{XRSpace}}'s <dfn for="XRSpace">coordinate system</dfn>.

The transform from the effective space to the [=native origin=]'s space is defined by an <dfn for="XRSpace">origin offset</dfn>, which is an {{XRRigidTransform}} initially set to an [=identity transform=]. In other words, the [=effective origin=] can be obtained by [=multiply transforms|multiplying=] [=origin offset=] and the [=native origin=].

The [=effective origin=] of an {{XRSpace}} can only be observed in the coordinate system of another {{XRSpace}} as an {{XRPose}}, returned by an {{XRFrame}}'s {{XRFrame/getPose()}} method. The spatial relationship between {{XRSpace}}s MAY change between {{XRFrame}}s.

<div class="algorithm" data-algorithm="populate-the-pose">

To <dfn>populate the pose</dfn> of an {{XRSpace}} |space| in an {{XRSpace}} |baseSpace| at the time represented by an {{XRFrame}} |frame| into an {{XRPose}} |pose|, with an optional |force emulation| flag, the user agent MUST run the following steps:

  1. If |frame|'s [=XRFrame/active=] boolean is `false`, throw an {{InvalidStateError}} and abort these steps.
  1. Let |session| be |frame|'s {{XRFrame/session}} object.
  1. If |space|'s [=XRSpace/session=] does not equal |session|, throw an {{InvalidStateError}} and abort these steps.
  1. If |baseSpace|'s [=XRSpace/session=] does not equal |session|, throw an {{InvalidStateError}} and abort these steps.
  1. Check if [=poses may be reported=] and, if not, throw a {{SecurityError}} and abort these steps.
  1. Let |limit| be the result of whether [=poses must be limited=] between |space| and |baseSpace|.
  1. Let |transform| be |pose|'s {{XRPose/transform}}.
  1. Query the [=/XR device=]'s tracking system for |space|'s pose relative to |baseSpace| at the |frame|'s [=XRFrame/time=], then perform the following steps:
    <dl class="switch">
      : If |limit| is `false` and the tracking system provides a [=6DoF=] pose whose position is actively tracked or statically known for |space|'s pose relative to |baseSpace|:
      :: Set |transform|'s {{XRRigidTransform/orientation}} to the orientation of |space|'s [=effective origin=] in |baseSpace|'s [=coordinate system=].
      :: Set |transform|'s {{XRRigidTransform/position}} to the position of |space|'s [=effective origin=] in |baseSpace|'s [=coordinate system=].
      :: Set |pose|'s {{XRPose/emulatedPosition}} to `false`.

      : Else if |limit| is `false` and the tracking system provides a [=3DoF=] pose or a [=6DoF=] pose whose position is neither actively tracked nor statically known for |space|'s pose relative to |baseSpace|:
      :: Set |transform|'s {{XRRigidTransform/orientation}} to the orientation of |space|'s [=effective origin=] in |baseSpace|'s [=coordinate system=].
      :: Set |transform|'s {{XRRigidTransform/position}} to the tracking system's best estimate of the position of |space|'s [=effective origin=] in |baseSpace|'s [=coordinate system=]. This MAY include a computed offset such as a neck or arm model. If a position estimate is not available, the last known position MUST be used.
      :: Set |pose|'s {{XRPose/emulatedPosition}} to `true`.

      : Else if |space|'s pose relative to |baseSpace| has been determined in the past and |force emulation| is `true`:
      :: Set |transform|'s {{XRRigidTransform/position}} to the last known position of |space|'s [=effective origin=] in |baseSpace|'s [=coordinate system=].
      :: Set |transform|'s {{XRRigidTransform/orientation}} to the last known orientation of |space|'s [=effective origin=] in |baseSpace|'s [=coordinate system=].
      :: Set |pose|'s {{XRPose/emulatedPosition}} boolean to `true`.

      : Otherwise:
      :: Set |pose| to `null`.

    </dl>

Note: The {{XRPose}}'s {{XRPose/emulatedPosition}} boolean does not indicate whether |baseSpace|'s position is emulated or not, only whether evaluating |space|'s position relative to |baseSpace| relies on emulation. For example, a controller with [=3DoF=] tracking would report poses with an {{XRPose/emulatedPosition}} of `true` when its {{targetRaySpace}} or {{gripSpace}} are queried against an {{XRReferenceSpace}}, but would report an {{XRPose/emulatedPosition}} of `false` if the pose of the {{targetRaySpace}} was queried in {{gripSpace}}, because the relationship between those two spaces should be known exactly.

</div>

XRReferenceSpace {#xrreferencespace-interface}
----------------

An {{XRReferenceSpace}} is one of several common {{XRSpace}}s that applications can use to establish a spatial relationship with the user's physical environment.

{{XRReferenceSpace}}s are generally expected to remain static for the duration of the {{XRSession}}, with the most common exception being mid-session reconfiguration by the user. The [=native origin=] for every {{XRReferenceSpace}} describes a coordinate system where `+X` is considered "Right", `+Y` is considered "Up", and `-Z` is considered "Forward".

<pre class="idl">
enum XRReferenceSpaceType {
  "viewer",
  "local",
  "local-floor",
  "bounded-floor",
  "unbounded"
};

[SecureContext, Exposed=Window]
interface XRReferenceSpace : XRSpace {
  [NewObject] XRReferenceSpace getOffsetReferenceSpace(XRRigidTransform originOffset);

  attribute EventHandler onreset;
};
</pre>

Each {{XRReferenceSpace}} has a <dfn for="XRReferenceSpace">type</dfn>, which is an {{XRReferenceSpaceType}}.

An {{XRReferenceSpace}} is most frequently obtained by calling {{XRSession/requestReferenceSpace()}}, which creates an instance of an {{XRReferenceSpace}} (or an interface extending it) if the {{XRReferenceSpaceType}} enum value passed into the call [=reference space is supported|is supported=]. The type indicates the tracking behavior that the reference space will exhibit:

  - Passing a type of <dfn enum-value for="XRReferenceSpaceType">viewer</dfn> creates an {{XRReferenceSpace}} instance. It represents a tracking space with a [=native origin=] which tracks the position and orientation of the [=viewer=]. Every {{XRSession}} MUST support {{XRReferenceSpaceType/"viewer"}} {{XRReferenceSpace}}s.

  - Passing a type of <dfn enum-value for="XRReferenceSpaceType">local</dfn> creates an {{XRReferenceSpace}} instance. It represents a tracking space with a [=native origin=] near the viewer at the time of creation. The exact position and orientation will be initialized based on the conventions of the underlying platform. When using this reference space the user is not expected to move beyond their initial position much, if at all, and tracking is optimized for that purpose. For devices with [=6DoF=] tracking, {{local}} reference spaces should emphasize keeping the origin stable relative to the user's environment.

  - Passing a type of <dfn enum-value for="XRReferenceSpaceType">local-floor</dfn> creates an {{XRReferenceSpace}} instance. It represents a tracking space with a [=native origin=] at the floor in a safe position for the user to stand. The `Y` axis equals `0` at floor level, with the `X` and `Z` position and orientation initialized based on the conventions of the underlying platform. If the floor level isn't known it MUST be estimated, with some <dfn>estimated floor level</dfn>. If the [=estimated floor level=] is determined with a non-default value, it MUST be [=rounding|rounded=] sufficiently to prevent fingerprinting. When using this reference space the user is not expected to move beyond their initial position much, if at all, and tracking is optimized for that purpose. For devices with [=6DoF=] tracking, {{local-floor}} reference spaces should emphasize keeping the origin stable relative to the user's environment.

    Note: If the floor level of a {{XRReferenceSpaceType/"local-floor"}} reference space is adjusted to prevent fingerprinting, [=rounding|rounded=] to the nearest 1cm is suggested.

  - Passing a type of <dfn enum-value for="XRReferenceSpaceType">bounded-floor</dfn> creates an {{XRBoundedReferenceSpace}} instance. It represents a tracking space with its [=native origin=] at the floor, where the user is expected to move within a pre-established boundary, given as the {{boundsGeometry}}. Tracking in a {{bounded-floor}} reference space is optimized for keeping the [=native origin=] and {{boundsGeometry}} stable relative to the user's environment.

  - Passing a type of <dfn enum-value for="XRReferenceSpaceType">unbounded</dfn> creates an {{XRReferenceSpace}} instance. It represents a tracking space where the user is expected to move freely around their environment, potentially even long distances from their starting point. Tracking in an {{unbounded}} reference space is optimized for stability around the user's current position, and as such the [=native origin=] may drift over time.

Devices that support {{XRReferenceSpaceType/"local"}} reference spaces MUST support {{XRReferenceSpaceType/"local-floor"}} reference spaces, through emulation if necessary, and vice versa.

The <dfn attribute for="XRReferenceSpace">onreset</dfn> attribute is an [=Event handler IDL attribute=] for the {{reset}} event type.

<div class="algorithm" data-algorithm="create-reference-space">

When an {{XRReferenceSpace}} is requested with {{XRReferenceSpaceType}} |type| for {{XRSession}} |session|, the user agent MUST <dfn>create a reference space</dfn> by running the following steps:

  1. Initialize |referenceSpace| as follows:
      <dl class="switch">
        : If |type| is {{bounded-floor}}:
        :: Let |referenceSpace| be a [=new=] {{XRBoundedReferenceSpace}} in the [=relevant realm=] of |session|.

        : Otherwise:
        :: Let |referenceSpace| be a [=new=] {{XRReferenceSpace}} in the [=relevant realm=] of |session|.

      </dl>
  1. Initialize |referenceSpace|'s [=XRReferenceSpace/type=] to |type|.
  1. Initialize |referenceSpace|'s [=XRSpace/session=] to |session|.
  1. Return |referenceSpace|.

</div>

<div class="algorithm" data-algorithm="reference-space-supported">
To check if a <dfn>reference space is supported</dfn> for a given reference space type |type| and {{XRSession}} |session|, run the following steps:

  1. If |type| is not [=list/contain|contained=] in |session|'s [=XRSession/XR device=]'s [=XR device/list of enabled features=] for [=XRSession/mode=] return `false`.
  1. If |type| is {{viewer}}, return `true`.
  1. If |type| is {{local}} or {{local-floor}}, and |session| is an [=immersive session=], return `true`.
  1. If |type| is {{local}} or {{local-floor}}, and the [=XRSession/XR device=] supports reporting orientation data, return `true`.
  1. If |type| is {{bounded-floor}} and |session| is an [=immersive session=], return the result of whether [=bounded reference spaces are supported=] by the [=XRSession/XR device=].
  1. If |type| is {{unbounded}}, |session| is an [=immersive session=], and the [=XRSession/XR device=] supports stable tracking near the user over an unlimited distance, return `true`.
  1. Return `false`.

</div>

<div class="algorithm" data-algorithm="get-offset-space">
The <dfn method for="XRReferenceSpace">getOffsetReferenceSpace(|originOffset|)</dfn> method MUST perform the following steps when invoked:

  1. Let |base| be the {{XRReferenceSpace}} the method was called on.
  1. Initialize |offsetSpace| as follows:
    <dl class="switch">
      : If |base| is an instance of {{XRBoundedReferenceSpace}}:
      :: Let |offsetSpace| be a [=new=] {{XRBoundedReferenceSpace}} in the [=relevant realm=] of |base|, and set |offsetSpace|'s {{boundsGeometry}} to |base|'s {{boundsGeometry}}, with each point multiplied by the {{XRRigidTransform/inverse}} of |originOffset|.

      : Otherwise:
      :: Let |offsetSpace| be a [=new=] {{XRReferenceSpace}} in the [=relevant realm=] of |base|.

    </dl>
  1. Set |offsetSpace|'s [=XRReferenceSpace/type=] to |base|'s [=XRReferenceSpace/type=].
  1. Set |offsetSpace|'s [=origin offset=] to the result of [=multiply transforms|multiplying=] |base|'s [=origin offset=] by |originOffset| in the [=relevant realm=] of |base|.
  1. Return |offsetSpace|.

</div>

Note: It's expected that some applications will use {{getOffsetReferenceSpace()}} to implement scene navigation controls based on mouse, keyboard, touch, or gamepad input. This will result in {{getOffsetReferenceSpace()}} being called frequently, at least once per-frame during periods of active input. As a result UAs are strongly encouraged to make the creation of new {{XRReferenceSpace}}s with {{getOffsetReferenceSpace()}} a lightweight operation.

XRBoundedReferenceSpace {#xrboundedreferencespace-interface}
-----------------------

{{XRBoundedReferenceSpace}} extends {{XRReferenceSpace}} to include {{boundsGeometry}}, indicating the pre-configured boundaries of the users space.

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRBoundedReferenceSpace : XRReferenceSpace {
  readonly attribute FrozenArray&lt;DOMPointReadOnly&gt; boundsGeometry;
};
</pre>

The origin of an {{XRBoundedReferenceSpace}} MUST be positioned at the floor, such that the `Y` axis equals `0` at floor level. The `X` and `Z` position and orientation are initialized based on the conventions of the underlying platform, typically expected to be near the center of the room facing in a logical forward direction.

Note: Other XR platforms sometimes refer to the type of tracking offered by a {{bounded-floor}} reference space as "room scale" tracking. An {{XRBoundedReferenceSpace}} is not intended to describe multi-room spaces, areas with uneven floor levels, or very large open areas. Content that needs to handle those scenarios should use an {{unbounded}} reference space.

Each {{XRBoundedReferenceSpace}} has a <dfn for="XRBoundedReferenceSpace">native bounds geometry</dfn> describing the border around the {{XRBoundedReferenceSpace}}, which the user can expect to safely move within. The polygonal boundary is given as an array of {{DOMPointReadOnly}}s, which represents a loop of points at the edges of the safe space. The points describe offsets from the [=XRSpace/native origin=] in meters. Points MUST be given in a clockwise order as viewed from above, looking towards the negative end of the Y axis. The {{DOMPointReadOnly/y}} value of each point MUST be `0` and the {{DOMPointReadOnly/w}} value of each point MUST be `1`. The bounds can be considered to originate at the floor and extend infinitely high. The shape it describes MAY be convex or concave.

Each point in the [=native bounds geometry=] MUST be [=limiting|limited=] to a reasonable distance from the reference space's [=native origin=].

Note: It is suggested that points of the [=native bounds geometry=] be [=limiting|limited=] to 15 meters from the [=XRSpace/native origin=] in all directions.

Each point in the [=native bounds geometry=] MUST also be [=quantization|quantized=] sufficiently to prevent fingerprinting.  For user's safety, quantized points values MUST NOT fall outside the bounds reported by the platform.

Note: It is suggested that points of the [=native bounds geometry=] be [=quantization|quantized=] to the nearest 5cm.

The <dfn attribute for="XRBoundedReferenceSpace">boundsGeometry</dfn> attribute is an array of {{DOMPointReadOnly}}s such that each entry is equal to the entry in the {{XRBoundedReferenceSpace}}'s [=XRBoundedReferenceSpace/native bounds geometry=] premultiplied by the {{XRRigidTransform/inverse}} of the [=XRSpace/origin offset=]. In other words, it provides the same border in {{XRBoundedReferenceSpace}} coordinates relative to the [=XRSpace/effective origin=].

If the [=native bounds geometry=] is temporarily unavailable, which may occur for several reasons such as during XR device initialization, extended periods of tracking loss, or movement between pre-configured spaces, the {{boundsGeometry}} MUST report an empty array.


<div class="algorithm" data-algorithm="bounded-space-supported">
To check if <dfn>bounded reference spaces are supported</dfn> run the following steps:
    1. If the [=XRSession/XR device=] cannot report boundaries, return false.
    1. If the [=XRSession/XR device=] cannot identify the height of the user's physical floor, return false.
    1. Return true.
</div>

Note: Bounded references spaces may be returned if the boundaries or floor height have not been resolved at the time of the reference space request, but the XR device is known to support them.

Note: Content should not require the user to move beyond the {{boundsGeometry}}. It is possible for the user to move beyond the bounds if their physical surroundings allow for it, resulting in position values outside of the polygon they describe. This is not an error condition and should be handled gracefully by page content.

Note: Content generally should not provide a visualization of the {{boundsGeometry}}, as it's the user agent's responsibility to ensure that safety critical information is provided to the user.

Views {#views}
=====

XRView {#xrview-interface}
------

An {{XRView}} describes a single [=view=] into an XR scene for a given frame.

A <dfn>view</dfn> corresponds to a display or portion of a display used by an XR device to present imagery to the user. They are used to retrieve all the information necessary to render content that is well aligned to the [=view=]'s physical output properties, including the field of view, eye offset, and other optical properties. [=Views=] may cover overlapping regions of the user's vision. No guarantee is made about the number of [=views=] any XR device uses or their order, nor is the number of [=views=] required to be constant for the duration of an {{XRSession}}.

A [=view=] has an associated internal <dfn>view offset</dfn>, which is an {{XRRigidTransform}} describing the position and orientation of the [=view=] in the [=XRSession/viewer reference space=]'s [=coordinate system=].

NOTE: There are no constraints on what the view offset might be, and views are allowed to have differing orientations. This can crop up in head-mounted devices with eye displays centered at an angle, and it can also surface itself in more extreme cases like CAVE rendering. Techniques like z-sorting and culling may need to be done per-eye because of this.

A [=view=] has an associated <dfn for="view">projection matrix</dfn> which is a [=matrix=] describing the projection to be used when rendering the [=view=], provided by the underlying XR device. The [=view/projection matrix=] MAY include transformations such as shearing that prevent the projection from being accurately described by a simple frustum.

A [=view=] has an associated <dfn for="view">eye</dfn> which is an {{XREye}} describing which eye this view is expected to be shown to. If the view does not have an intrinsically associated eye (the display is monoscopic, for example) this value MUST be set to {{XREye/"none"}}.

A [=view=] has an <dfn for="view">active</dfn> flag that may change through the lifecycle of an {{XRSession}}. [=Primary views=] MUST always have the [=view/active=] flag set to `true`.

Note: Many HMDs will request that content render two [=views=], one for the left eye and one for the right, while most magic window devices will only request one [=view=], but applications should never assume a specific view configuration. For example: A magic window device may request two views if it is capable of stereo output, but may revert to requesting a single view for performance reasons if the stereo output mode is turned off. Similarly, HMDs may request more than two views to facilitate a wide field of view or displays of different pixel density.

A [=view=] has an internal <dfn for="view">viewport modifiable</dfn> flag that indicates if the viewport scale can be changed by a {{XRView/requestViewportScale()}} call at this point in the session. It is set to `true` at the start of an animation frame, and set to `false` when {{XRWebGLLayer/getViewport()}} is called.

A [=view=] has an internal <dfn for="view">requested viewport scale</dfn> value that represents the requested viewport scale for this view. It is initially set to 1.0, and can be modified by the {{XRView/requestViewportScale()}} method if the system supports dynamic viewport scaling.

A [=view=] has an internal <dfn for="view">current viewport scale</dfn> value that represents the current viewport scale for this view as used internally by the system. It is initially set to 1.0. It is updated to match the [=view/requested viewport scale=] when the viewport change is successfully applied by a {{XRWebGLLayer/getViewport()}} call.

Note: Dynamic viewport scaling allows applications to render to a subset of the full-sized viewport using a scale factor that can be changed every animation frame. This is intended to be efficiently modifiable on a per-frame basis without reallocation. For correct rendering, it's essential that the XR system and application agree on the active viewport. An application can call {{XRView/requestViewportScale()}} for an {{XRView}} multiple times within a single animation frame, but the requested scale does not take effect until the application calls {{XRWebGLLayer/getViewport()}} for that view. The first `getViewport` call in an animation frame applies the change (taking effect immediately for the current animation frame), locks in the view's current scaled viewport for the remainder of this animation frame, and sets the scale as the new default for future animation frames. Optionally, the system can provide a suggested value through the {{XRView/recommendedViewportScale}} attribute based on internal performance heuristics and target framerates.

<pre class="idl">
enum XREye {
  "none",
  "left",
  "right"
};

[SecureContext, Exposed=Window] interface XRView {
  readonly attribute XREye eye;
  readonly attribute Float32Array projectionMatrix;
  [SameObject] readonly attribute XRRigidTransform transform;
  readonly attribute double? recommendedViewportScale;

  undefined requestViewportScale(double? scale);
};
</pre>

The <dfn attribute for="XRView">eye</dfn> attribute describes is the [=view/eye=] of the underlying [=view=]. This attribute's primary purpose is to ensure that pre-rendered stereo content can present the correct portion of the content to the correct eye.

The <dfn attribute for="XRView">projectionMatrix</dfn> attribute is the [=view/projection matrix=] of the underlying [=view=]. It is <b>strongly recommended</b> that applications use this matrix without modification or decomposition. Failure to use the provided projection matrices when rendering may cause the presented frame to be distorted or badly aligned, resulting in varying degrees of user discomfort. This attribute MUST be computed by [=XRView/obtain the projection matrix|obtaining the projection matrix=] for the {{XRView}}.

The <dfn attribute for="XRView">transform</dfn> attribute is the {{XRRigidTransform}} of the viewpoint. It represents the position and orientation of the viewpoint in the {{XRReferenceSpace}} provided in {{XRFrame/getViewerPose()}}.

The optional <dfn attribute for="XRView">recommendedViewportScale</dfn> attribute contains a UA-recommended viewport scale value that the application can use for a {{XRView/requestViewportScale()}} call to configure dynamic viewport scaling. It is `null` if the system does not implement a heuristic or method for determining a recommended scale. If not null, the value MUST be a numeric value greater than 0.0 and less than or equal to 1.0.

Each {{XRView}} has an associated <dfn for="XRView">session</dfn> which is the {{XRSession}} that produced it.

Each {{XRView}} has an associated <dfn for="XRView">frame time</dfn> which is the [=XRFrame/time=] of the {{XRFrame}} that produced it.

Each {{XRView}} has an associated <dfn for="XRView">underlying view</dfn> which is the underlying [=view=] that it represents.

Each {{XRView}} has an associated <dfn for="XRView">internal projection matrix</dfn> which stores the [=view/projection matrix=] of its [=XRView/underlying view=]. It is initially `null`.

Note: The {{XRView/transform}} can be used to position camera objects in many rendering libraries. If a more traditional view matrix is needed by the application one can be retrieved by calling `view.transform.inverse.matrix`.

<div class="algorithm" data-algorithm="request-viewport-scale">

The <dfn method for="XRView">requestViewportScale(|scale|)</dfn> method requests that the user agent should set the [=view/requested viewport scale=] for this viewport to the requested value.

When this method is invoked on an {{XRView}} |xrview|, the user agent MUST run the following steps:

  1. If |scale| is null or undefined, abort these steps.
  1. If |scale| is less than or equal to 0.0, abort these steps.
  1. If |scale| is greater than 1.0, set |scale| to 1.0.
  1. Let |view| be |xrview|'s [=XRView/underlying view=].
  1. Set the |view|'s [=view/requested viewport scale=] value to |scale|.

Note: The method ignores null or undefined scale values so that applications can safely use `view.requestViewportScale(view.recommendedViewportScale)` even on systems that don't provide a recommended scale.

</div>

<div class=algorithm data-algorithm="obtain-xrview-projection">

To <dfn for="XRView">obtain the projection matrix</dfn> for a given {{XRView}} |view|

  1. If |view|'s [=XRView/internal projection matrix=] is not `null`, perform the following steps:
    1. If the operation {{IsDetachedBuffer}} on [=XRView/internal projection matrix=]  is `false`, return |view|'s [=XRView/internal projection matrix=].
  1. Set |view|'s [=XRView/internal projection matrix=] to a [=new=] [=matrix=] in the [=relevant realm=] of |view| which is equal to |view|'s [=XRView/underlying view=]'s [=view/projection matrix=].
  1. Return |view|'s [=XRView/internal projection matrix=].

</div>

<div class=algorithm data-algorithm="update-the-viewports">

When the [=view/active=] flag of any [=view=] in the [=XRSession/list of views=] changes, one can <dfn>update the viewports</dfn> for an {{XRSession}} |session| by performing the following steps:

  1. Let |layer| be the {{XRSession/renderState}}'s {{XRRenderState/baseLayer}}.
  1. If |layer| is `null` abort these steps.
  1. Set |layer|'s [=list of viewport objects=] to the empty [=/list=].
  1. For each [=view/active=] [=view=] |view| in [=XRSession/list of views=]:
    1. Let |viewport| be the {{XRViewport}} result of [=obtain a scaled viewport|obtaining a scaled viewport=] from the [=list of full-sized viewports=] associated with |view| for |session|.
    1. [=list/Append=] |viewport| to |layer|'s [=list of viewport objects=].

</div>

<div class=algorithm data-algorithm="obtain-scaled-viewport">

To <dfn for="XRView">obtain a scaled viewport</dfn> for a given {{XRView}} |view| for an {{XRSession}} |session|

    1. Let |glFullSizedViewport| be the [=WebGL viewport=] from the [=list of full-sized viewports=] associated with |view|.
    1. Let |scale| be the |view|'s [=view/current viewport scale=].
    1. The user-agent MAY choose to clamp |scale| to apply a minimum viewport scale factor.
    1. Let |glViewport| be a new [=WebGL viewport=].
    1. Set |glViewport|'s `width` to an integer value less than or equal to |glFullSizedViewport|'s `width` multiplied by |scale|.
    1. If |glViewport|'s `width` is less than 1, set it to 1.
    1. Set |glViewport|'s `height` to an integer value less than or equal to |glFullSizedViewport|'s `height` multiplied by |scale|.
    1. If |glViewport|'s `height` is less than 1, set it to 1.
    1. Set |glViewport|'s `x` component to an integer value between |glFullSizedViewport|'s `x` component (inclusive) and |glFullSizedViewport|'s `x` component plus |glFullSizedViewport|'s `width` minus |glViewport|'s `width` (inclusive).
    1. Set |glViewport|'s `y` component to a integer value between |glFullSizedViewport|'s `y` component (inclusive) and |glFullSizedViewport|'s `y` component plus |glFullSizedViewport|'s `height` minus |glViewport|'s `height` (inclusive).
    1. Let |viewport| be a [=new=] {{XRViewport}} in the [=relevant realm=] of |session|.
    1. Initialize |viewport|'s {{XRViewport/x}} to |glViewport|'s `x` component.
    1. Initialize |viewport|'s {{XRViewport/y}} to |glViewport|'s `y` component.
    1. Initialize |viewport|'s {{XRViewport/width}} to |glViewport|'s `width`.
    1. Initialize |viewport|'s {{XRViewport/height}} to |glViewport|'s `height`.
    1. Return |viewport|.

Note: The specific integer value calculation is intentionally left to the UA's discretion. The straightforward method of rounding down the width/height and using the `x` and `y` offsets as-is is valid, but the UA MAY also choose a slightly adjusted value within the specified constraints, for example to align the viewport to a power-of-two pixel grid for efficiency. The scaled viewport MUST be completely contained within the full-sized viewport, but MAY be placed at any location within the full-sized viewport at the UA's discretion. The size and position calculation MUST be deterministic and return a consistent result for identical input values within a session.

</div>

Primary and Secondary Views {#primary-and-secondary-views}
----------

A [=view=] is a <dfn>primary view</dfn> when rendering to it is necessary for an immersive experience. [=Primary views=] MUST be [=view/active=] for the entire duration of the {{XRSession}}.

A [=view=] is a <dfn>secondary view</dfn> when it is possible for content to choose to not render to it and still produce a working immersive experience. When content chooses to not render to these views, the user-agent MAY be able to reconstruct them via reprojection. Secondary views MUST NOT be [=view/active=] unless the "[=secondary view/secondary-views=]" feature is enabled.

<div class="example">
Examples of [=primary views=] include the main mono view for a handheld AR session, the main two stereo views for headworn AR/VR sessions, or all of the wall views for a CAVE session.

Examples of [=secondary views=] include the [=first-person observer view=] used for video capture, or "quad views" where there are two views per eye with differing resolution and fields of view.

</div>

<div class=note>
While content should be written to assume that there may be any number of views, we expect a significant amount of content to make incorrect assumptions about the {{XRViewerPose/views}} array and thus break when presented with more than two views.

Because user-agents may have the ability to use mechanisms like reprojection to render to these [=secondary views=] in lieu of the content, it is desirable to be able to distinguish between content that plans on handling these [=secondary views=] itself and content that is either oblivious to the existence of such [=secondary views=] or does not wish to deal with them.
</div>

To provide for this, user-agents that expose [=secondary views=] MUST support an "<dfn for="secondary view">secondary-views</dfn>" [=feature descriptor=] as a hint. Content enabling this feature is expected to:

 - Handle any nonzero number of [=views=] in the {{XRViewerPose/views}} array.
 - Handle the existence of multiple [=views=] that have the same [=view/eye=].
 - Handle the size of the {{XRViewerPose/views}} array changing from frame to frame. <span class=note>This can happen when video capture is enabled, for example</span>

When "[=secondary view/secondary-views=]" is enabled, the user-agent MAY surface any [=secondary views=] the device supports to the {{XRSession}}, when necessary. The user-agent MUST NOT use reprojection to reconstruct [=secondary views=] in such a case, and instead rely on whatever the content decides to render.

Note: We recommend content use {{XRSessionInit/optionalFeatures}} to enable "[=secondary view/secondary-views=]" to ensure maximum compatibility.

If [=secondary views=] have lower underlying frame rates, the {{XRSession}} MAY choose to do one or more of the following:

 - Lower the overall frame rate of the application while the [=secondary views=] are active.
 - Surface [=secondary views=] in the {{XRViewerPose/views}} array only for some of the frames. Implementations doing this SHOULD NOT have frames where the [=primary views=] are not present.
 - Silently discard rendered content for [=secondary views=] during some of the frames.

XRViewport {#xrviewport-interface}
----------

An {{XRViewport}} object describes a viewport, or rectangular region, of a graphics surface.

<pre class="idl">
[SecureContext, Exposed=Window] interface XRViewport {
  readonly attribute long x;
  readonly attribute long y;
  readonly attribute long width;
  readonly attribute long height;
};
</pre>

The <dfn attribute for="XRViewport">x</dfn> and <dfn attribute for="XRViewport">y</dfn> attributes define an offset from the surface origin and the <dfn attribute for="XRViewport">width</dfn> and <dfn attribute for="XRViewport">height</dfn> attributes define the rectangular dimensions of the viewport.

The exact interpretation of the viewport values depends on the conventions of the graphics API the viewport is associated with:

 - When used with an {{XRWebGLLayer}} the {{XRViewport/x}} and {{XRViewport/y}} attributes specify the lower left corner of the viewport rectangle, in pixels, with the viewport rectangle extending {{XRViewport/width}} pixels to the right of {{XRViewport/x}} and {{XRViewport/height}} pixels above {{XRViewport/y}}. The values can be passed to the [=WebGL viewport=] function directly.

<div class="example">
The following code loops through all of the {{XRView}}s of an {{XRViewerPose}}, queries an {{XRViewport}} from an {{XRWebGLLayer}} for each, and uses them to set the appropriate [=WebGL viewport=]s for rendering.

<pre highlight="js">
xrSession.requestAnimationFrame((time, xrFrame) => {
  const viewer = xrFrame.getViewerPose(xrReferenceSpace);

  gl.bindFramebuffer(xrWebGLLayer.framebuffer);
  for (xrView of viewer.views) {
    let xrViewport = xrWebGLLayer.getViewport(xrView);
    gl.viewport(xrViewport.x, xrViewport.y, xrViewport.width, xrViewport.height);

    // WebGL draw calls will now be rendered into the appropriate viewport.
  }
});
</pre>
</div>

Geometric Primitives {#geometricprimitives}
====================

Matrices {#matrices}
--------

WebXR provides various transforms in the form of <dfn lt="matrix">matrices</dfn>. WebXR uses the WebGL conventions when communicating matrices, in which 4x4 matrices are given as 16 element {{Float32Array}}s with column major storage, and are applied to column vectors by premultiplying the matrix from the left. They may be passed directly to WebGL's {{uniformMatrix4fv}} function, used to create an equivalent {{DOMMatrix}}, or used with a variety of third party math libraries.

<div class="example">
Matrices returned from the WebXR Device API will be a 16 element {{Float32Array}} laid out like so:
<pre>
[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15]
</pre>

Applying this matrix as a transform to a column vector specified as a {{DOMPointReadOnly}} like so:
<pre>{x:X, y:Y, z:Z, w:1}</pre>

Produces the following result:
<pre>
a0 a4 a8  a12  *  X  =  a0 * X + a4 * Y +  a8 * Z + a12
a1 a5 a9  a13     Y     a1 * X + a5 * Y +  a9 * Z + a13
a2 a6 a10 a14     Z     a2 * X + a6 * Y + a10 * Z + a14
a3 a7 a11 a15     1     a3 * X + a7 * Y + a11 * Z + a15
</pre>
</div>

Normalization {#normalization}
-------------

There are several algorithms which call for a vector or quaternion to be normalized, which means to scale the components to have a collective magnitude of `1.0`.

<div class="algorithm" data-algorithm="normalize">

To <dfn>normalize</dfn> a list of components the UA MUST perform the following steps:

  1. Let |length| be the square root of the sum of the squares of each component.
  1. If |length| is `0`, throw an {{InvalidStateError}}  and abort these steps.
  1. Divide each component by |length| and set the component.

</div>

XRRigidTransform {#xrrigidtransform-interface}
----------------

An {{XRRigidTransform}} is a transform described by a {{XRRigidTransform/position}} and {{XRRigidTransform/orientation}}. When interpreting an {{XRRigidTransform}} the {{XRRigidTransform/orientation}} is always applied prior to the {{XRRigidTransform/position}}.

An {{XRRigidTransform}} contains an <dfn for="XRRigidTransform">internal matrix</dfn> which is a [=/matrix=].

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRRigidTransform {
  constructor(optional DOMPointInit position = {}, optional DOMPointInit orientation = {});
  [SameObject] readonly attribute DOMPointReadOnly position;
  [SameObject] readonly attribute DOMPointReadOnly orientation;
  readonly attribute Float32Array matrix;
  [SameObject] readonly attribute XRRigidTransform inverse;
};
</pre>

<div class="algorithm" data-algorithm="construct-rigid-transform">

The <dfn constructor for="XRRigidTransform">XRRigidTransform(|position|, |orientation|)</dfn> constructor MUST perform the following steps when invoked:

  1. Let |transform| be a [=new=] {{XRRigidTransform}} in the [=current realm=].
  1. Let |transform|'s {{XRRigidTransform/position}} be a [=new=] {{DOMPointReadOnly}} in the [=current realm=].
  1. If |position|'s {{DOMPointReadOnly/w}} value is not `1.0`, throw a {{TypeError}} and abort these steps.
  1. If one or more of |position|'s or |orientation|'s values is `NaN` or another non-finite number such as `infinity`, throw a {{TypeError}} and abort these steps.
  1. Set |transform|'s {{XRRigidTransform/position}}’s {{DOMPointReadOnly/x}} value to |position|'s x dictionary member, {{DOMPointReadOnly/y}} value to |position|'s y dictionary member, {{DOMPointReadOnly/z}} value to |position|'s z dictionary member and {{DOMPointReadOnly/w}} value to |position|'s w dictionary member.
  1. Let |transform|'s {{XRRigidTransform/orientation}} be a [=new=] {{DOMPointReadOnly}} in the [=current realm=].
  1. Set |transform|'s {{XRRigidTransform/orientation}}’s {{DOMPointReadOnly/x}} value to |orientation|'s x dictionary member, {{DOMPointReadOnly/y}} value to |orientation|'s y dictionary member, {{DOMPointReadOnly/z}} value to |orientation|'s z dictionary member and {{DOMPointReadOnly/w}} value to |orientation|'s w dictionary member.
  1. Let |transform|'s [=XRRigidTransform/internal matrix=] be `null`.
  1. [=Normalize=] {{DOMPointReadOnly/x}}, {{DOMPointReadOnly/y}}, {{DOMPointReadOnly/z}}, and {{DOMPointReadOnly/w}} components of |transform|'s {{XRRigidTransform/orientation}}.
  1. Return |transform|.

</div>

The <dfn attribute for="XRRigidTransform">position</dfn> attribute is a 3-dimensional point, given in meters, describing the translation component of the transform. The {{XRRigidTransform/position}}'s {{DOMPointReadOnly/w}} attribute MUST be `1.0`.

The <dfn attribute for="XRRigidTransform">orientation</dfn> attribute is a quaternion describing the rotational component of the transform. The {{XRRigidTransform/orientation}} MUST be normalized to have a length of `1.0`.

The <dfn attribute for="XRRigidTransform">matrix</dfn> attribute returns the transform described by the {{XRRigidTransform/position}} and {{XRRigidTransform/orientation}} attributes as a [=matrix=]. This attribute MUST be computed by [=XRRigidTransform/obtain the matrix|obtaining the matrix=] for the {{XRRigidTransform}}.

Note: This matrix when premultiplied onto a column vector will rotate the vector by the 3D rotation described by {{XRRigidTransform/orientation}}, and then translate it by {{XRRigidTransform/position}}. Mathematically in column-vector notation, this is `M = T * R`, where `T` is a translation matrix corresponding to {{XRRigidTransform/position}} and  `R` is a rotation matrix corresponding to {{XRRigidTransform/orientation}}.

<div class=algorithm data-algorithm="obtain-xrrigidtransform-matrix">

To <dfn for="XRRigidTransform">obtain the matrix</dfn> for a given {{XRRigidTransform}} |transform|

  1. If |transform|'s [=XRRigidTransform/internal matrix=] is not `null`, perform the following steps:
    1. If the operation {{IsDetachedBuffer}} on [=XRRigidTransform/internal matrix=] is `false`, return |transform|'s [=XRRigidTransform/internal matrix=].
  1. Let |translation| be a new [=matrix=] which is a column-vector translation matrix corresponding to {{XRRigidTransform/position}}. Mathematically, if {{XRRigidTransform/position}} is `(x, y, z)`, this matrix is

        <img src="images/translation_matrix.svg" alt="Mathematical expression for column-vector translation matrix" />
  1. Let |rotation| be a new [=matrix=] which is a column-vector rotation matrix corresponding to {{XRRigidTransform/orientation}}. Mathematically, if {{XRRigidTransform/orientation}} is the unit quaternion `(q<sub>x</sub>, q<sub>y</sub>, q<sub>z</sub>, q<sub>w</sub>)`, this matrix is

        <img src="images/rotation_matrix.svg" alt="Mathematical expression for column-vector rotation matrix" />
  1. Set |transform|'s [=XRRigidTransform/internal matrix=] to a [=new=] {{Float32Array}} in the [=relevant realm=] of |transform| set to the result of multiplying |translation| and |rotation| with |translation| on the left (`translation * rotation`) in the [=relevant realm=] of |transform|. Mathematically, this matrix is

       <img src="images/rigid_matrix.svg" alt="Mathematical expression for matrix of multiplying translation and rotation with translation on the left" />
  1. Return |transform|'s [=XRRigidTransform/internal matrix=].

</div>

The <dfn attribute for="XRRigidTransform">inverse</dfn> attribute of a {{XRRigidTransform}} |transform| returns an {{XRRigidTransform}} in the relevant realm of |transform| which, if applied to an object that had previously been transformed by |transform|, would undo the transform and return the object to its initial pose. This attribute SHOULD be lazily evaluated. The {{XRRigidTransform}} returned by {{inverse}} MUST return |transform| as its {{inverse}}.

An {{XRRigidTransform}} with a {{XRRigidTransform/position}} of `{ x: 0, y: 0, z: 0 w: 1 }` and an {{XRRigidTransform/orientation}} of `{ x: 0, y: 0, z: 0, w: 1 }` is known as an <dfn>identity transform</dfn>.

<div class="algorithm" data-algorithm="multiply transforms">

To <dfn lt="multiply transforms">multiply two {{XRRigidTransform}}s</dfn>, |B| and |A| in a [=Realm=] |realm|, the UA MUST perform the following steps:

  1. Let |result| be a [=new=] {{XRRigidTransform}} object in |realm|.
  1. Set |result|'s {{XRRigidTransform/matrix}} to a [=new=] {{Float32Array}} in |realm|, the result of premultiplying |B|'s {{XRRigidTransform/matrix}} from the left onto |A|'s {{XRRigidTransform/matrix}}.
  1. Set |result|'s {{XRRigidTransform/orientation}} to a [=new=] {{DOMPointReadOnly}} in |realm|, the quaternion that describes the rotation indicated by the top left 3x3 sub-matrix of |result|'s {{XRRigidTransform/matrix}}.
  1. Set |result|'s {{XRRigidTransform/position}} to  a [=new=] {{DOMPointReadOnly}} in |realm|, the vector given by the fourth column of |result|'s {{XRRigidTransform/matrix}}.
  1. Return |result|.

|result| is a transform from |A|'s source space to |B|'s destination space.

Note: This is equivalent to constructing an {{XRRigidTransform}} whose {{XRRigidTransform/orientation}} is the composition of the orientation of |A| and |B|, and whose {{XRRigidTransform/position}} is equal to |A|'s {{XRRigidTransform/position}} rotated by |B|'s {{XRRigidTransform/orientation}}, added to |B|'s {{XRRigidTransform/position}}.
</div>

Pose {#pose}
====

XRPose {#xrpose-interface}
------

An {{XRPose}} describes a position and orientation in space relative to an {{XRSpace}}.

<pre class="idl">
[SecureContext, Exposed=Window] interface XRPose {
  [SameObject] readonly attribute XRRigidTransform transform;
  readonly attribute boolean emulatedPosition;
};
</pre>

The <dfn attribute for="XRPose">transform</dfn> attribute describes the position and orientation relative to the base {{XRSpace}}.

The <dfn attribute for="XRPose">emulatedPosition</dfn> attribute is `false` when the {{XRPose/transform}} represents an actively tracked [=6DoF=] pose based on sensor readings, or `true` if its {{XRRigidTransform/position}} value includes a <dfn for="XRPose">computed offset</dfn>, such as that provided by a neck or arm model. [=Estimated floor level=]s MUST NOT be considered when determining if an {{XRPose}} includes a [=XRPose/computed offset=].

XRViewerPose {#xrviewerpose-interface}
------------

An {{XRViewerPose}} is an {{XRPose}} describing the state of a <dfn>viewer</dfn> of the XR scene as tracked by the [=XRSession/XR device=]. A [=viewer=] may represent a tracked piece of hardware, the observed position of a users head relative to the hardware, or some other means of computing a series of viewpoints into the XR scene. {{XRViewerPose}}s can only be queried relative to an {{XRReferenceSpace}}. It provides, in addition to the {{XRPose}} values, an array of [=view=]s which include rigid transforms to indicate the viewpoint and projection matrices. These values should be used by the application when rendering a frame of an XR scene.

<pre class="idl">
[SecureContext, Exposed=Window] interface XRViewerPose : XRPose {
  [SameObject] readonly attribute FrozenArray&lt;XRView&gt; views;
};
</pre>

The <dfn attribute for="XRViewerPose">views</dfn> array is a sequence of {{XRView}}s describing the viewpoints of the XR scene, relative to the {{XRReferenceSpace}} the {{XRViewerPose}} was queried with. Every [=view=] of the XR scene in the array must be rendered in order to display correctly on the [=XRSession/XR device=]. Each {{XRView}} includes rigid transforms to indicate the viewpoint and projection matrices, and can be used to query {{XRViewport}}s from layers when needed.

Note: The {{XRViewerPose}}'s {{XRPose/transform}} can be used to position graphical representations of the [=viewer=] for spectator views of the scene or multi-user interaction.

Input {#input}
=====

XRInputSource {#xrinputsource-interface}
-------------

An {{XRInputSource}} represents an <dfn>XR input source</dfn>, which is any input mechanism which allows the user to perform targeted actions in the same virtual space as the [=viewer=]. Example [=XR input source=]s include, but are not limited to, handheld controllers, optically tracked hands, and gaze-based input methods that operate on the [=viewer=]'s pose. Input mechanisms which are not explicitly associated with the [=/XR device=], such as traditional gamepads, mice, or keyboards SHOULD NOT be considered [=XR input source=]s.

<pre class="idl">
enum XRHandedness {
  "none",
  "left",
  "right"
};

enum XRTargetRayMode {
  "gaze",
  "tracked-pointer",
  "screen"
};

[SecureContext, Exposed=Window]
interface XRInputSource {
  readonly attribute XRHandedness handedness;
  readonly attribute XRTargetRayMode targetRayMode;
  [SameObject] readonly attribute XRSpace targetRaySpace;
  [SameObject] readonly attribute XRSpace? gripSpace;
  [SameObject] readonly attribute FrozenArray&lt;DOMString&gt; profiles;
};
</pre>

Note: The {{XRInputSource}} interface is also extended by the <a href="https://immersive-web.github.io/webxr-gamepads-module/">WebXR Gamepads Module</a>

The <dfn attribute for="XRInputSource">handedness</dfn> attribute describes which hand the [=XR input source=] is associated with, if any. Input sources with no natural handedness (such as headset-mounted controls) or for which the handedness is not currently known MUST set this attribute {{XRHandedness/"none"}}.

The <dfn attribute for="XRInputSource">targetRayMode</dfn> attribute describes the method used to produce the target ray, and indicates how the application should present the target ray to the user if desired.

  - <dfn enum-value for="XRTargetRayMode">gaze</dfn> indicates the target ray will originate at the viewer and follow the direction it is facing. (This is commonly referred to as a "gaze input" device in the context of head-mounted displays.)
  - <dfn enum-value for="XRTargetRayMode">tracked-pointer</dfn> indicates that the target ray originates from either a handheld device or other hand-tracking mechanism and represents that the user is using their hands or the held device for pointing. The orientation of the target ray relative to the tracked object MUST follow platform-specific ergonomics guidelines when available. In the absence of platform-specific guidance, the target ray SHOULD point in the same direction as the user's index finger if it was outstretched.
  - <dfn enum-value for="XRTargetRayMode">screen</dfn> indicates that the input source was an interaction with the canvas element associated with an inline session's output context, such as a mouse click or touch event.

The <dfn attribute for="XRInputSource">targetRaySpace</dfn> attribute is an {{XRSpace}} that has a [=native origin=] tracking the position and orientation of the preferred pointing ray of the {{XRInputSource}} (along its -Z axis), as defined by the {{targetRayMode}}.

The <dfn attribute for="XRInputSource">gripSpace</dfn> attribute is an {{XRSpace}} that has a [=native origin=] tracking to the pose that should be used to render virtual objects such that they appear to be held in the user's hand. If the user were to hold a straight rod, this {{XRSpace}} places the [=native origin=] at the centroid of their curled fingers and where the `-Z` axis points along the length of the rod towards their thumb. The `X` axis is perpendicular to the back of the hand being described, with back of the users right hand pointing towards `+X` and the back of the user's left hand pointing towards `-X`. The `Y` axis is implied by the relationship between the `X` and `Z` axis, with `+Y` roughly pointing in the direction of the user's arm.

The {{gripSpace}} MUST be `null` if the input source isn't inherently trackable such as for input sources with a {{targetRayMode}} of {{XRTargetRayMode/"gaze"}} or {{XRTargetRayMode/"screen"}}.

The <dfn attribute for="XRInputSource">profiles</dfn> attribute is a [=/list=] of [=input profile name=]s indicating both the prefered visual representation and behavior of the input source.

An <dfn for="XRInputSource">input profile name</dfn> is an ASCII lowercase {{DOMString}} containing no spaces, with separate words concatenated with a hyphen (`-`) character. A descriptive name should be chosen, using the prefered verbiage of the device vendor when possible. If the platform provides an appropriate identifier, such as a USB vendor and product ID, it MAY be used. Values that uniquely identify a single device, such as serial numbers, MUST NOT be used. The [=input profile name=] MUST NOT contain an indication of device handedness. If multiple user agents expose the same device, they SHOULD make an effort to report the same [=input profile name=]. The <a href="https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/registry">WebXR Input Profiles Registry</a> is the recommended location for managing [=input profile name=]s.

Profiles are given in descending order of specificity. Any [=input profile name=]s given after the first entry in the list should provide fallback values that represent alternative representations of the device. This may include a more generic or prior version of the device, a more widely recognized device that is sufficiently similar, or a broad description of the device type (such as "generic-trigger-touchpad"). If multiple profiles are given, the layouts they describe must all represent a superset or subset of every other profile in the list.

If the {{XRSession}}'s [=mode=] is {{XRSessionMode/"inline"}}, {{XRInputSource/profiles}} MUST be an empty list.

The user agent MAY choose to only report an appropriate generic [=input profile name=]s or an empty list at its discretion. Some scenarios where this would be appropriate are if the input device cannot be reliably identified, no known input profiles match the input device, or the user agent wishes to mask the input device being used.

<p class="note">
For example, the Samsung HMD Odyssey's controller is a design variant of the standard Windows Mixed Reality controller. Both controllers share the same input layout. As a result, the {{XRInputSource/profiles}} for a Samsung HMD Odyssey controller could be: `["samsung-odyssey", "microsoft-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]`. The appearance of the controller is most precisely communicated by the first profile in the list, with the second profile describing an acceptable substitute, and the last profile a generic fallback that describes the device in the roughest sense. (It's a controller with a trigger, squeeze button, touchpad and thumbstick.)
</br></br>
Similarly, the Valve Index controller is backwards compatible with the HTC Vive controller, but the Index controller has additional buttons and axes. As a result, the {{XRInputSource/profiles}} for the Valve Index controller could be: `["valve-index", "htc-vive", "generic-trigger-squeeze-touchpad-thumbstick"]`. In this case the input layout described by the `"valve-index"` profile is a superset of the layout described by the `"htc-vive"` profile. Also, the `"valve-index"` profile indicates the precise appearance of the controller, while the `"htc-vive"` controller has a significantly different appearance. In this case the UA would have deemed that difference acceptable. And as in the first example, the last profile is a generic fallback.
</br></br>
(Exact strings are examples only. Actual profile names are managed in the <a href="https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/registry">WebXR Input Profiles Registry</a>.)
</p>

Note: {{XRInputSource}}s in an {{XRSession}}'s {{XRSession/inputSources}} array are "live". As such values within them are updated in-place. This means that it doesn't work to save a reference to an {{XRInputSource}}'s attribute on one frame and compare it to the same attribute in a subsequent frame to test for state changes, because they will be the same object. Therefore developers that wish to compare input state from frame to frame should copy the content of the state in question.

An [=XR input source=] is a <dfn>primary input source</dfn> if it supports a <dfn>primary action</dfn>. The [=primary action=] is a platform-specific action that, when engaged, produces {{XRSession/selectstart}}, {{XRSession/selectend}}, and {{XRSession/select}} events. Examples of possible [=primary action=]s are pressing a trigger, touchpad, or button, speaking a command, or making a hand gesture. If the platform guidelines define a recommended primary input then it should be used as the [=primary action=], otherwise the user agent is free to select one. The device MUST support at least one [=primary input source=].

An [=XR input source=] is an <dfn>auxiliary input source</dfn> if it does not support a [=primary action=], for example [=transient input sources=] associated with secondary screen touches on a multitouch device.

<div class="algorithm" data-algorithm="on-input-start">

When an [=XR input source=] |source| for {{XRSession}} |session| begins its [=primary action=] the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| with [=XRFrame/time=] being the time the action occurred.
  1. [=Queue a task=] to [=fire an input source event=] with name {{selectstart!!event}}, frame |frame|, and source |source|.

</div>

<div class="algorithm" data-algorithm="on-input-end">

When an [=XR input source=] |source| for {{XRSession}} |session| ends its [=primary action=] the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| with [=XRFrame/time=] being the time the action occurred.
  1. [=Queue a task=] to perform the following steps:
    1. [=Fire an input source event=] with name {{select!!event}}, frame |frame|, and source |source|.
    1. [=Fire an input source event=] with name {{selectend!!event}}, frame |frame|, and source |source|.

</div>

Each [=XR input source=] MAY define a <dfn>primary squeeze action</dfn>. The [=primary squeeze action=] is a platform-specific action that, when engaged, produces {{XRSession/squeezestart}}, {{XRSession/squeezeend}}, and {{XRSession/squeeze}} events. The [=primary squeeze action=] should be used for actions roughly mapping to squeezing or grabbing. Examples of possible [=primary squeeze action=]s are pressing a grip trigger or making a grabbing hand gesture. If the platform guidelines define a recommended primary squeeze action then it should be used as the [=primary squeeze action=], otherwise the user agent MAY select one.

<div class="algorithm" data-algorithm="on-squeeze-start">

When an [=XR input source=] |source| for {{XRSession}} |session| begins its [=primary squeeze action=] the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| with [=XRFrame/time=] being the time the action occurred.
  1. [=Queue a task=] to [=fire an input source event=] with name {{squeezestart!!event}}, frame |frame|, and source |source|.

</div>

<div class="algorithm" data-algorithm="on-squeeze-end">

When an [=XR input source=] |source| for {{XRSession}} |session| ends its [=primary squeeze action=] the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| with [=XRFrame/time=] being the time the action occurred.
  1. [=Queue a task=] to perform the following steps:
    1. [=Fire an input source event=] with name {{squeeze!!event}}, frame |frame|, and source |source|.
    1. [=Fire an input source event=] with name {{squeezeend!!event}}, frame |frame|, and source |source|.

</div>

Sometimes platform-specific behavior can result in a [=primary action=] or [=primary squeeze action=] being interrupted or cancelled. For example, a [=/XR input source=] may be removed from the [=XRSession/XR device=] after the [=primary action=] or [=primary squeeze action=] is started but before it ends.

<div class="algorithm" data-algorithm="on-input-cancelled">

When an [=XR input source=] |source| for {{XRSession}} |session| has its [=primary action=] cancelled the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| with [=XRFrame/time=] being the time the action occurred.
  1. [=Queue a task=] to [=fire an input source event=] an {{XRInputSourceEvent}} with name {{selectend!!event}}, frame |frame|, and source |source|.

</div>

<div class="algorithm" data-algorithm="on-squeeze-cancelled">

When an [=XR input source=] |source| for {{XRSession}} |session| has its [=primary squeeze action=] cancelled the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| with [=XRFrame/time=] being the time the action occurred.
  1. [=Queue a task=] to [=fire an input source event=] an {{XRInputSourceEvent}} with name {{squeezeend!!event}}, frame |frame|, and source |source|.

</div>

Transient input {#transient-input}
---------------

Some [=/XR device=]s may support <dfn>transient input sources</dfn>, where the [=XR input source=] is only meaningful while performing a <dfn>transient action</dfn>, either the [=primary action=] for a [=primary input source=], or a device-specific <dfn>auxiliary action</dfn> for an [=auxiliary input source=]. An example would be mouse, touch, or stylus input against an {{XRSessionMode/"inline"}} {{XRSession}}, which MUST produce a transient {{XRInputSource}} with a {{targetRayMode}} set to {{screen}}, treated as a [=primary action=] for the [=primary pointer=], and as a non-primary [=auxiliary action=] for a non-primary pointer. [=Transient input sources=] are only present in the session's [=list of active XR input sources=] for the duration of the [=transient action=].

[=Transient input sources=] follow the following sequence when handling [=transient actions=] instead of the algorithms for non-transient [=primary actions=]:

<div class="algorithm" data-algorithm="on-transient-input-start">

When a [=transient input source=] |source| for {{XRSession}} |session| begins its [=transient action=] the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| for the time the action occurred.
  1. [=Queue a task=] to perform the following steps:
    1. Fire any `"pointerdown"` events produced by the [=XR input source=]'s action, if necessary.
    1. [=add input source|Add the XR input source=] to the [=list of active XR input sources=].
    1. If the [=transient action=] is a [=primary action=], [=fire an input source event=] with name {{selectstart!!event}}, frame |frame|, and source |source|.

</div>

<div class="algorithm" data-algorithm="on-transient-input-end">

When a [=transient input source=] |source| for {{XRSession}} |session| ends its [=transient action=] the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| for the time the action occurred.
  1. [=Queue a task=] to perform the following steps:
    1. If the [=transient action=] is a [=primary action=], [=fire an input source event=] with name {{select!!event}}, frame |frame|, and source |source|.
    1. Fire any `"click"` events produced by the [=XR input source=]'s action, if necessary.
    1. If the [=transient action=] is a [=primary action=], [=fire an input source event=] with name {{selectend!!event}}, frame |frame|, and source |source|.
    1. [=remove input source|Remove the XR input source=] from the [=list of active XR input sources=].
    1. Fire any `"pointerup"` events produced by the [=XR input source=]'s action, if necessary.

</div>

<div class="algorithm" data-algorithm="on-transient-input-cancelled">

When a [=transient input source=] |source| for {{XRSession}} |session| has its [=transient action=] cancelled the UA MUST run the following steps:

  1. Let |frame| be a [=new=] {{XRFrame}} in the [=relevant realm=] of |session| with {{XRFrame/session}} |session| for the time the action occurred.
  1. [=Queue a task=] to perform the following steps:
    1. If the [=transient action=] is a [=primary action=], [=fire an input source event=] with name {{selectend!!event}}, frame |frame|, and source |source|.
    1. [=remove input source|Remove the XR input source=] from the [=list of active XR input sources=].
    1. Fire any `"pointerup"` events produced by the [=XR input source=]'s action, if necessary.

</div>

XRInputSourceArray {#xrinputsourcearray-interface}
------------------

An {{XRInputSourceArray}} represents a [=/list=] of {{XRInputSource}}s. It is used in favor of a [=/frozen array type=] when the contents of the [=/list=] are expected to change over time, such as with the {{XRSession}} {{XRSession/inputSources}} attribute.

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRInputSourceArray {
  iterable&lt;XRInputSource&gt;;
  readonly attribute unsigned long length;
  getter XRInputSource(unsigned long index);
};
</pre>

The <dfn attribute for="XRInputSourceArray">length</dfn> attribute of {{XRInputSourceArray}} indicates how many {{XRInputSource}}s are contained within the {{XRInputSourceArray}}.

The <dfn export for="XRInputSourceArray">[=/indexed property getter=]</dfn> of {{XRInputSourceArray}} retrieves the {{XRInputSource}} at the provided index.

Layers {#layers}
======

Note: While this specification only defines the {{XRWebGLLayer}} layer, future extensions to the spec are expected to add additional layer types and the image sources that they draw from.

XRLayer {#xrlayer-interface}
-------

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRLayer : EventTarget {};

</pre>

{{XRLayer}} is the base class for {{XRWebGLLayer}} and other layer types introduced by future extensions.

XRWebGLLayer {#xrwebgllayer-interface}
------------

An {{XRWebGLLayer}} is a layer which provides a WebGL framebuffer to render into, enabling hardware accelerated rendering of 3D graphics to be presented on the [=XRSession/XR device=].

<pre class="idl">
typedef (WebGLRenderingContext or
         WebGL2RenderingContext) XRWebGLRenderingContext;

dictionary XRWebGLLayerInit {
  boolean antialias = true;
  boolean depth = true;
  boolean stencil = false;
  boolean alpha = true;
  boolean ignoreDepthValues = false;
  double framebufferScaleFactor = 1.0;
};

[SecureContext, Exposed=Window]
interface XRWebGLLayer: XRLayer {
  constructor(XRSession session,
             XRWebGLRenderingContext context,
             optional XRWebGLLayerInit layerInit = {});
  // Attributes
  readonly attribute boolean antialias;
  readonly attribute boolean ignoreDepthValues;

  [SameObject] readonly attribute WebGLFramebuffer? framebuffer;
  readonly attribute unsigned long framebufferWidth;
  readonly attribute unsigned long framebufferHeight;

  // Methods
  XRViewport? getViewport(XRView view);

  // Static Methods
  static double getNativeFramebufferScaleFactor(XRSession session);
};
</pre>

Each {{XRWebGLLayer}} has a <dfn for="XRWebGLLayer">context</dfn> object, initially `null`, which is an instance of either a {{WebGLRenderingContext}} or a {{WebGL2RenderingContext}}.

Each {{XRWebGLLayer}} has an associated <dfn for="XRWebGLLayer">session</dfn>, which is the {{XRSession}} it was created with.

<div class="algorithm" data-algorithm="construct-webgl-layer">

The <dfn constructor for="XRWebGLLayer">XRWebGLLayer(|session|, |context|, |layerInit|)</dfn> constructor MUST perform the following steps when invoked:

  1. Let |layer| be a [=new=] {{XRWebGLLayer}} in the [=relevant realm=] of |session|.
  1. If |session|'s [=ended=] value is `true`, throw an {{InvalidStateError}} and abort these steps.
  1. If |context| is lost, throw an {{InvalidStateError}} and abort these steps.
  1. If |session| is an [=immersive session=] and |context|'s [=XR compatible=] boolean is `false`, throw an {{InvalidStateError}} and abort these steps.
  1. Initialize |layer|'s [=XRWebGLLayer/context=] to |context|.
  1. Initialize |layer|'s [=XRWebGLLayer/session=] to |session|.
  1. Initialize |layer|'s {{XRWebGLLayer/ignoreDepthValues}} as follows:
    <dl class="switch">
      : If |layerInit|'s {{XRWebGLLayerInit/ignoreDepthValues}} value is `false` and the [=XR Compositor=] will make use of depth values:
      :: Initialize |layer|'s {{XRWebGLLayer/ignoreDepthValues}} to `false`.

      : Otherwise:
      :: Initialize |layer|'s {{XRWebGLLayer/ignoreDepthValues}} to `true`.

    </dl>
  1. Initialize |layer|'s [=XRWebGLLayer/composition disabled=] boolean as follows:
    <dl class="switch">
      : If |session| is an [=inline session=]:
      :: Initialize |layer|'s [=XRWebGLLayer/composition disabled=] to `true`.

      : Otherwise:
      :: Initialize |layer|'s [=XRWebGLLayer/composition disabled=] boolean to `false`.

    </dl>
  1. <dl class="switch">
      : If |layer|'s [=XRWebGLLayer/composition disabled=] boolean is `false`:
      ::
        1. Initialize |layer|'s {{XRWebGLLayer/antialias}} to |layerInit|'s {{XRWebGLLayerInit/antialias}} value.
        1. Let |scaleFactor| be |layerInit|'s {{XRWebGLLayerInit/framebufferScaleFactor}}.
        1. The user-agent MAY choose to clamp or round |scaleFactor| as it sees fit here, for example if it wishes to fit the buffer dimensions into a power of two for performance reasons.
        1. Let |framebufferSize| be the [=recommended WebGL framebuffer resolution=] with width and height separately multiplied by |scaleFactor|.
        1. Initialize |layer|'s {{XRWebGLLayer/framebuffer}} to a [=new=] {{WebGLFramebuffer}} in the [=relevant realm=] of |context|, which is an [=opaque framebuffer=] with the dimensions |framebufferSize| created with |context|, [=opaque framebuffer/session=] initialized to |session|, and |layerInit|'s {{XRWebGLLayerInit/depth}}, {{XRWebGLLayerInit/stencil}}, and {{XRWebGLLayerInit/alpha}} values.
        1. Allocate and initialize resources compatible with |session|'s [=XRSession/XR device=], including GPU accessible memory buffers, as required to support the compositing of |layer|.
        1. If |layer|’s resources were unable to be created for any reason, throw an {{OperationError}} and abort these steps.

      : Otherwise:
      ::
        1. Initialize |layer|'s {{XRWebGLLayer/antialias}} to |layer|'s {{XRWebGLLayer/context}}'s [=actual context parameters=] {{WebGLContextAttributes|antialias}} value.
        1. Initialize |layer|'s {{XRWebGLLayer/framebuffer}} to `null`.

    </dl>
  1. Return |layer|.

</div>

Note: If an {{XRWebGLLayer}}'s [=XRWebGLLayer/composition disabled=] boolean is set to `true` all values on the {{XRWebGLLayerInit}} object are ignored, since the {{WebGLRenderingContext}}'s default framebuffer was already allocated using the context's [=actual context parameters=] and cannot be overridden.

The <dfn attribute for="XRWebGLLayer">context</dfn> attribute is the {{WebGLRenderingContext}} the {{XRWebGLLayer}} was created with.

Each {{XRWebGLLayer}} has a <dfn for="XRWebGLLayer">composition disabled</dfn> boolean which is initially set to `false`. If set to `true` it indicates that the {{XRWebGLLayer}} MUST NOT allocate its own {{WebGLFramebuffer}}, and all properties of the {{XRWebGLLayer}} that reflect {{XRWebGLLayer/framebuffer}} properties MUST instead reflect the properties of the [=XRWebGLLayer/context=]'s default framebuffer.

The <dfn attribute for="XRWebGLLayer">framebuffer</dfn> attribute of an {{XRWebGLLayer}} is an instance of a {{WebGLFramebuffer}} which has been marked as [=opaque framebuffer|opaque=] if [=XRWebGLLayer/composition disabled=] is `false`, and `null` otherwise. The {{framebuffer}} size cannot be adjusted by the developer after the {{XRWebGLLayer}} has been created.

An <dfn>opaque framebuffer</dfn> functions identically to a standard {{WebGLFramebuffer}} with the following changes that make it behave more like the [=default framebuffer=]:

 - An [=opaque framebuffer=] MAY support antialiasing, even in WebGL 1.0.
 - An [=opaque framebuffer=]'s attachments cannot be inspected or changed. Calling {{framebufferTexture2D}}, {{framebufferRenderbuffer}}, {{deleteFramebuffer}}, or {{getFramebufferAttachmentParameter}} with an [=opaque framebuffer=] MUST generate an {{INVALID_OPERATION}} error.
 - An [=opaque framebuffer=] has a related <dfn for="opaque framebuffer">session</dfn>, which is the {{XRSession}} it was created for.
 - An [=opaque framebuffer=] is considered incomplete outside of a {{XRSession/requestAnimationFrame()}} callback. When not in the {{XRSession/requestAnimationFrame()}} callback of its [=opaque framebuffer/session=], calls to {{checkFramebufferStatus}} MUST generate a {{FRAMEBUFFER_UNSUPPORTED}} error and attempts to clear, draw to, or read from the [=opaque framebuffer=] MUST generate an {{INVALID_FRAMEBUFFER_OPERATION}} error.
 - An [=opaque framebuffer=] initialized with {{XRWebGLLayerInit/depth}} `true` will have an attached depth buffer.
 - An [=opaque framebuffer=] initialized with {{XRWebGLLayerInit/stencil}} `true` will have an attached stencil buffer.
 - An [=opaque framebuffer=]'s color buffer will have an alpha channel if and only if {{XRWebGLLayerInit/alpha}} is `true`.
 - The [=XR Compositor=] will assume the [=opaque framebuffer=] contains colors with premultiplied alpha. This is true regardless of the {{WebGLContextAttributes|premultipliedAlpha}} value set in the {{XRWebGLLayer/context}}'s [=actual context parameters=].

Note: User agents are required to respect `true` values of {{XRWebGLLayerInit/depth}} and {{XRWebGLLayerInit/stencil}}, which is similar to WebGL's behavior when [=create a drawing buffer|creating a drawing buffer=]

The buffers attached to an [=opaque framebuffer=] MUST be cleared to the values in the table below when first created, or prior to the processing of each [=XR animation frame=]. This is identical to the behavior of the WebGL context's [=default framebuffer=]. [=Opaque framebuffers=] will always be cleared regardless of the associated WebGL context's {{WebGLContextAttributes|preserveDrawingBuffer}} value.

<table class="tg">
  <thead>
    <tr>
      <th>Buffer</th>
      <th>Clear Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Color</td>
      <td>(0, 0, 0, 0)</td>
    </tr>
    <tr>
      <td>Depth</td>
      <td>1.0</td>
    </tr>
    <tr>
      <td>Stencil</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

Note: Implementations may optimize away the required implicit clear operation of the [=opaque framebuffer=] as long as a guarantee can be made that the developer cannot gain access to buffer contents from another process. For instance, if the developer performs an explicit clear then the implicit clear is not needed.

When an {{XRWebGLLayer}} is set as an [=immersive session=]'s {{XRRenderState/baseLayer}} the content of the [=opaque framebuffer=] is presented to the [=immersive XR device=] immediately after an [=XR animation frame=] completes, but only if at least one of the following has occurred since the previous [=XR animation frame=]:

  - The [=immersive session=]'s {{XRRenderState/baseLayer}} was changed.
  - {{clear}}, {{drawArrays}}, {{drawElements}}, or any other rendering operation which similarly affects the framebuffer's color values has been called while the [=opaque framebuffer=] is the currently bound framebuffer of the {{WebGLRenderingContext}} associated with the {{XRWebGLLayer}}.

Before the [=opaque framebuffer=] is presented to the [=immersive XR device=] the user agent shall ensure that all rendering operations have been flushed to the [=opaque framebuffer=].

Each {{XRWebGLLayer}} has a <dfn for="XRWebGLLayer">target framebuffer</dfn>, which is the {{XRWebGLLayer/framebuffer}} if [=XRWebGLLayer/composition disabled=] is `false`, and the [=XRWebGLLayer/context=]'s default framebuffer otherwise.

The <dfn attribute for="XRWebGLLayer">framebufferWidth</dfn> and <dfn attribute for="XRWebGLLayer">framebufferHeight</dfn> attributes return the width and height of the [=XRWebGLLayer/target framebuffer=]'s attachments, respectively.

The <dfn attribute for="XRWebGLLayer">antialias</dfn> attribute is `true` if the [=XRWebGLLayer/target framebuffer=] supports antialiasing using a technique of the UAs choosing, and `false` if no antialiasing will be performed.

The <dfn attribute for="XRWebGLLayer">ignoreDepthValues</dfn> attribute, if `true`, indicates the [=XR Compositor=] MUST NOT make use of values in the depth buffer attachment when rendering. When the attribute is `false` it indicates that the content of the depth buffer attachment will be used by the [=XR Compositor=] and is expected to be representative of the scene rendered into the layer.

Depth values stored in the buffer are expected to be between `0.0` and `1.0`, with `0.0` representing the distance of {{XRRenderState/depthNear}} and `1.0` representing the distance of {{XRRenderState/depthFar}}, with intermediate values interpolated linearly. This is the default behavior of WebGL. (See documentation for the <a href="https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glDepthRangef.xml">depthRange function</a> for additional details.))

Note: Making the scene's depth buffer available to the compositor allows some platforms to provide quality and comfort improvements such as improved reprojection.

Each {{XRWebGLLayer}} MUST have a <dfn>list of full-sized viewports</dfn> which is a [=/list=] containing one [=WebGL viewport=] for each [=view=] the {{XRSession}} may expose, including [=secondary views=] that are not currently [=view/active=] but may become [=view/active=] for the current session. The viewports MUST have a {{XRViewport/width}} and {{XRViewport/height}} greater than `0` and MUST describe a rectangle that does not exceed the bounds of the [=target framebuffer=]. The viewports MUST NOT be overlapping. If [=XRWebGLLayer/composition disabled=] is `true`, the [=list of full-sized viewports=] MUST contain a single [=WebGL viewport=] that covers the [=XRWebGLLayer/context=]'s entire default framebuffer.

Each {{XRWebGLLayer}} MUST have a <dfn>list of viewport objects</dfn> which is a [=/list=] containing one {{XRViewport}} for each [=view/active=] [=view=] the {{XRSession}} currently exposes.

{{getViewport()}} queries the {{XRViewport}} the given {{XRView}} should use when rendering to the layer.

<div class="algorithm" data-algorithm="get-viewport">

The <dfn method for="XRWebGLLayer">getViewport(|view|)</dfn> method, when invoked on an {{XRWebGLLayer}} |layer|, MUST run the following steps:

  1. Let |session| be |view|'s [=XRView/session=].
  1. Let |frame| be |session|'s [=XRSession/animation frame=].
  1. If |session| is not equal to |layer|'s [=XRWebGLLayer/session=], throw an {{InvalidStateError}} and abort these steps.
  1. If |frame|'s [=XRFrame/active=] boolean is `false`, throw an {{InvalidStateError}} and abort these steps.
  1. If |view|'s [=XRView/frame time=] is not equal to |frame|'s [=XRFrame/time=], throw an {{InvalidStateError}} and abort these steps.
  1. If the [=view/viewport modifiable=] flag is `true` and |view|'s [=view/requested viewport scale=] is not equal to [=view/current viewport scale=]:
    1. Set [=view/current viewport scale=] to [=view/requested viewport scale=].
    1. In the [=list of viewport objects=], set the {{XRViewport}} associated with |view| to the {{XRViewport}} result of [=obtain a scaled viewport|obtaining a scaled viewport=] from the [=list of full-sized viewports=] associated with |view| for |session|.
  1. Set the |view|'s [=view/viewport modifiable=] flag to false.
  1. Let |viewport| be the {{XRViewport}} from the [=list of viewport objects=] associated with |view|.
  1. Return |viewport|.

Note: The [=view/viewport modifiable=] flag is intentionally set to false even if there was no change to the [=view/current viewport scale=]. This ensures that `getViewport(view)` calls always return consistent results within an animation frame for that view, so the first retrieved value is locked in for the remainder of the frame. If an application calls {{XRView/requestViewportScale()}} after `getViewport()`, the requested value is only applied later when `getViewport()` is called again in a future frame.

</div>

Each {{XRSession}} MUST identify a <dfn>native WebGL framebuffer resolution</dfn>, which is the pixel resolution of a WebGL framebuffer required to match the physical pixel resolution of the [=XRSession/XR device=].

<div class="algorithm" data-algorithm="native-webgl-framebuffer-resolution">

The [=native WebGL framebuffer resolution=] for an {{XRSession}} |session| is determined by running the following steps:

  1. If |session|'s [=XRSession/mode=] value is not {{XRSessionMode/"inline"}}, set the [=native WebGL framebuffer resolution=] to the resolution required to have a 1:1 ratio between the pixels of a framebuffer large enough to contain all of the session's {{XRView}}s and the physical screen pixels in the area of the display under the highest magnification and abort these steps. If no method exists to determine the native resolution as described, the [=recommended WebGL framebuffer resolution=] MAY be used.
  1. If |session|'s [=XRSession/mode=] value is {{XRSessionMode/"inline"}}, set the [=native WebGL framebuffer resolution=] to the size of the |session|'s {{XRSession/renderState}}'s [=XRRenderState/output canvas=] in physical display pixels and reevaluate these steps every time the size of the canvas changes or the [=XRRenderState/output canvas=] is changed.

</div>

Additionally, the {{XRSession}} MUST identify a <dfn>recommended WebGL framebuffer resolution</dfn>, which represents a best estimate of the WebGL framebuffer resolution large enough to contain all of the session's {{XRView}}s that provides an average application a good balance between performance and quality. It MAY be smaller than, larger than, or equal to the [=native WebGL framebuffer resolution=]. New [=opaque framebuffer=] will be created with this resolution, with width and height each scaled by any {{XRWebGLLayerInit}}'s {{XRWebGLLayerInit/framebufferScaleFactor}} provided.

Note: The user agent is free to use any method of its choosing to estimate the [=recommended WebGL framebuffer resolution=]. If there are platform-specific methods for querying a recommended size it is recommended that they be used, but not required. The scale factors used by {{XRWebGLLayerInit/framebufferScaleFactor}} and {{XRWebGLLayer/getNativeFramebufferScaleFactor}} apply to width and height separately, so a scale factor of two results in four times the overall pixel count. If the platform exposes an area-based render scale that's based on pixel count, the user agent needs to take the square root of that to convert it to a WebXR scale factor.

<div class="algorithm" data-algorithm="get-native-framebuffer-scale-factor">

The <dfn method for="XRWebGLLayer">getNativeFramebufferScaleFactor(|session|)</dfn> method, when invoked, MUST run the following steps:

  1. Let |session| be [=this=].
  1. If |session|'s [=ended=] value is `true`, return `0.0` and abort these steps.
  1. Return the value that the |session|'s [=recommended WebGL framebuffer resolution=] width and height must each be multiplied by to yield the |session|'s [=native WebGL framebuffer resolution=].

</div>

WebGL Context Compatibility {#contextcompatibility}
---------------------------

In order for a WebGL context to be used as a source for immersive XR imagery it must be created on a <dfn>compatible graphics adapter</dfn> for the [=immersive XR device=]. What is considered a [=compatible graphics adapter=] is platform dependent, but is understood to mean that the graphics adapter can supply imagery to the [=immersive XR device=] without undue latency. If a WebGL context was not already created on the [=compatible graphics adapter=], it typically must be re-created on the adapter in question before it can be used with an {{XRWebGLLayer}}.

Note: On an XR platform with a single GPU, it can safely be assumed that the GPU is compatible with the [=immersive XR device=]s advertised by the platform, and thus any hardware accelerated WebGL contexts are compatible as well. On PCs with both an integrated and discrete GPU the discrete GPU is often considered the [=compatible graphics adapter=] since it generally a higher performance chip. On desktop PCs with multiple graphics adapters installed, the one with the [=immersive XR device=] physically connected to it is likely to be considered the [=compatible graphics adapter=].

Note: {{XRSessionMode/"inline"}} sessions render using the same graphics adapter as canvases, and thus do not need {{WebGLContextAttributes/xrCompatible}} contexts.

<pre class="idl">
partial dictionary WebGLContextAttributes {
    boolean xrCompatible = false;
};

partial interface mixin WebGLRenderingContextBase {
    [NewObject] Promise&lt;undefined&gt; makeXRCompatible();
};
</pre>

When a user agent implements this specification it MUST set a <dfn>XR compatible</dfn> boolean, initially set to `false`, on every {{WebGLRenderingContextBase}}. Once the [=XR compatible=] boolean is set to `true`, the context can be used with layers for any {{XRSession}} requested from the current [=immersive XR device=].

Note: This flag introduces slow synchronous behavior and is discouraged. Consider calling {{WebGLRenderingContextBase/makeXRCompatible()}} instead for an asynchronous solution.

The [=XR compatible=] boolean can be set either at context creation time or after context creation, potentially incurring a context loss. To set the [=XR compatible=] boolean at context creation time, the {{xrCompatible}} context creation attribute must be set to `true` when requesting a WebGL context.   If the requesting document's origin is not allowed to use the "xr-spatial-tracking" [[#permissions-policy|permissions policy]], {{xrCompatible}} has no effect.


The {{WebGLContextAttributes/xrCompatible}} flag on {{WebGLContextAttributes}}, if `true`, affects context creation by requesting the user-agent [=create the WebGL context=] using a [=compatible graphics adapter=] for the [=immersive XR device=]. If the user agent succeeds in this, the created context's [=XR compatible=] boolean will be set to true. To obtain the [=immersive XR device=], [=ensure an immersive XR device is selected=] SHOULD be called.

Note: [=Ensure an immersive XR device is selected=] needs to be run [=in parallel=], which introduces slow synchronous behavior on the main thread. User-agents SHOULD print a warning to the console requesting that {{WebGLRenderingContextBase/makeXRCompatible()}} be used instead.

<div class="example">
The following code creates a WebGL context that is compatible with an [=immersive XR device=] and then uses it to create an {{XRWebGLLayer}}.

<pre highlight="js">
function onXRSessionStarted(xrSession) {
  const glCanvas = document.createElement("canvas");
  const gl = glCanvas.getContext("webgl", { xrCompatible: true });

  loadWebGLResources();

  xrSession.updateRenderState({ baseLayer: new XRWebGLLayer(xrSession, gl) });
}
</pre>
</div>

To set the [=XR compatible=] boolean after the context has been created, the {{makeXRCompatible()}} method is used.

Note: On some systems this flag may turn on a high powered discrete GPU, for example, or proxy all commands to an on-device GPU. If you are in a situation where you may or may not be using XR, it is suggested that you only call {{makeXRCompatible()}} when you intend to start an [=immersive session=].

<div class="algorithm" data-algorithm="make-xr-compatible">
The <dfn method for="WebGLRenderingContextBase">makeXRCompatible()</dfn> method ensures the {{WebGLRenderingContextBase}} is running on a [=compatible graphics adapter=] for the [=immersive XR device=].

When this method is invoked, the user agent MUST run the following steps:

  1. If the requesting document's origin is not allowed to use the "xr-spatial-tracking" [[#permissions-policy|permissions policy]], [=/resolve=] |promise| and return it. <span class=note>When the XR permissions policy is disabled, we wish to behave as if there is no XR device in this case, since {{WebGLRenderingContextBase/makeXRCompatible()}} is supposed to be a set-and-forget method.</span>
  1. Let |promise| be [=a new Promise=] created in the [=Realm=] of this {{WebGLRenderingContextBase}}.
  1. Let |context| be [=this=].
  1. Run the following steps [=in parallel=]:
    1. Let |device| be the result of [=ensure an immersive XR device is selected|ensuring an immersive XR device is selected=].
    1. Set |context|'s [=XR compatible=] boolean as follows:
        <dl class="switch">
          : If |context|'s [=WebGL context lost flag=] is set:
          :: [=Queue a task=] to set |context|'s [=XR compatible=] boolean to `false` and [=reject=] |promise| with an {{InvalidStateError}}.

          : If |device| is `null`:
          :: [=Queue a task=] to set |context|'s [=XR compatible=] boolean to `false` and [=reject=] |promise| with an {{InvalidStateError}}.

          : If |context|'s [=XR compatible=] boolean is `true`:
          :: [=Queue a task=] to [=/resolve=] |promise|.

          : If |context| was created on a [=compatible graphics adapter=] for |device|:
          :: [=Queue a task=] to set |context|'s [=XR compatible=] boolean to `true` and [=/resolve=] |promise|.

          : Otherwise:
          :: [=Queue a task=] on the [=/WebGL task source=] to perform the following steps:
            1. Force |context| to be lost.
            1. [=Handle the context loss=] as described by the WebGL specification:
                1. Let |canvas| be the |context|'s [=WebGLRenderingContext/canvas=].
                1. If |context|'s [=WebGLRenderingContext/webgl context lost flag=] is set, abort these steps.
                1. Set |context|'s [=WebGLRenderingContext/webgl context lost flag=].
                1. Set the [=WebGLObject/invalidated=] flag of each {{WebGLObject}} instance created by |context|.
                1. Disable all extensions except "WEBGL_lose_context".
                1. [=Queue a task=] on the [=/WebGL task source=] to perform the following steps:
                    1. [=Fire a WebGL context event=] |e| named "webglcontextlost" at |canvas|, with {{WebGLContextEvent/statusMessage}} set to "".
                    1. If |e|'s [=canceled flag=] is not set, [=reject=] |promise| with an {{AbortError}} and abort these steps.
                    1. Run the following steps [=in parallel=].
                        1. Await a restorable drawing buffer on a [=compatible graphics adapter=] for |device|.
                        1. [=Queue a task=] on the [=/WebGL task source=] to perform the following steps:
                            1. [=Restore the context=] on a [=compatible graphics adapter=] for |device|.
                            1. Set |context|'s [=XR compatible=] boolean to `true`.
                            1. [=/Resolve=] |promise|.

        </dl>
  1. Return |promise|.

</div>

<div class="algorithm" data-algorithm="webgl-context-lost">

Additionally, when any WebGL [=handle the context loss|context is lost=] run the following steps prior to firing the "webglcontextlost" event:

  1. Set the context's [=XR compatible=] boolean to `false`.

</div>

<div class="example">
The following code creates an {{XRWebGLLayer}} from a pre-existing WebGL context.

<pre highlight="js">
const glCanvas = document.createElement("canvas");
const gl = glCanvas.getContext("webgl");

loadWebGLResources();

glCanvas.addEventListener("webglcontextlost", (event) => {
  // Indicates that the WebGL context can be restored.
  event.canceled = true;
});

glCanvas.addEventListener("webglcontextrestored", (event) => {
  // WebGL resources need to be re-created after a context loss.
  loadWebGLResources();
});

async function onXRSessionStarted(xrSession) {
  // Make sure the canvas context we want to use is compatible with the device.
  // May trigger a context loss.
  await gl.makeXRCompatible();
  xrSession.updateRenderState({ baseLayer: new XRWebGLLayer(xrSession, gl) });
}
</pre>
</div>

Events {#events}
======

The [=task source=] for all [=queue a task|tasks queued=] in this specification is the <dfn export>XR task source</dfn>, unless otherwise specified.

XRSessionEvent {#xrsessionevent-interface}
--------------

{{XRSessionEvent}}s are fired to indicate changes to the state of an {{XRSession}}.

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRSessionEvent : Event {
  constructor(DOMString type, XRSessionEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
};

dictionary XRSessionEventInit : EventInit {
  required XRSession session;
};
</pre>

The <dfn attribute for="XRSessionEvent">session</dfn> attribute indicates the {{XRSession}} that generated the event.

XRInputSourceEvent {#xrinputsourceevent-interface}
------------------

{{XRInputSourceEvent}}s are fired to indicate changes to the state of an {{XRInputSource}}.

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRInputSourceEvent : Event {
  constructor(DOMString type, XRInputSourceEventInit eventInitDict);
  [SameObject] readonly attribute XRFrame frame;
  [SameObject] readonly attribute XRInputSource inputSource;
};

dictionary XRInputSourceEventInit : EventInit {
  required XRFrame frame;
  required XRInputSource inputSource;
};
</pre>

The <dfn attribute for="XRInputSourceEvent">inputSource</dfn> attribute indicates the {{XRInputSource}} that generated this event.

The <dfn attribute for="XRInputSourceEvent">frame</dfn> attribute is an {{XRFrame}} that corresponds with the time that the event took place. It may represent historical data. {{XRFrame/getViewerPose()}} MUST throw an exception when called on {{XRInputSourceEvent/frame}}.

<div class="algorithm" data-algorithm="fire-input-source-event">

When the user agent has to <dfn>fire an input source event</dfn> with name |name|, {{XRFrame}} |frame|, and {{XRInputSource}} |source| it MUST run the following steps:

  1. Create an {{XRInputSourceEvent}} |event| with {{Event/type}} |name|, {{XRInputSourceEvent/frame}} |frame|, and {{XRInputSourceEvent/inputSource}} |source|.
  1. Set |frame|'s [=XRFrame/active=] boolean to `true`.
  1. [=XRFrame/Apply frame updates=] for |frame|.
  1. [=Dispatch=] |event| on |frame|'s {{XRFrame/session}}
  1. Set |frame|'s [=XRFrame/active=] boolean to `false`.

</div>


XRInputSourcesChangeEvent {#xrinputsourceschangeevent-interface}
-------------------------

{{XRInputSourcesChangeEvent}}s are fired to indicate changes to the {{XRInputSource}}s that are available to an {{XRSession}}.

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRInputSourcesChangeEvent : Event {
  constructor(DOMString type, XRInputSourcesChangeEventInit eventInitDict);
  [SameObject] readonly attribute XRSession session;
  [SameObject] readonly attribute FrozenArray&lt;XRInputSource&gt; added;
  [SameObject] readonly attribute FrozenArray&lt;XRInputSource&gt; removed;
};

dictionary XRInputSourcesChangeEventInit : EventInit {
  required XRSession session;
  required FrozenArray&lt;XRInputSource&gt; added;
  required FrozenArray&lt;XRInputSource&gt; removed;

};
</pre>

The <dfn attribute for="XRInputSourcesChangeEvent">session</dfn> attribute indicates the {{XRSession}} that generated the event.

The <dfn attribute for="XRInputSourcesChangeEvent">added</dfn> attribute is a [=/list=] of {{XRInputSource}}s that were added to the {{XRSession}} at the time of the event.

The <dfn attribute for="XRInputSourcesChangeEvent">removed</dfn> attribute is a [=/list=] of {{XRInputSource}}s that were removed from the {{XRSession}} at the time of the event.


XRReferenceSpaceEvent {#xrreferencespaceevent-interface}
---------------------

{{XRReferenceSpaceEvent}}s are fired to indicate changes to the state of an {{XRReferenceSpace}}.

<pre class="idl">
[SecureContext, Exposed=Window]
interface XRReferenceSpaceEvent : Event {
  constructor(DOMString type, XRReferenceSpaceEventInit eventInitDict);
  [SameObject] readonly attribute XRReferenceSpace referenceSpace;
  [SameObject] readonly attribute XRRigidTransform? transform;
};

dictionary XRReferenceSpaceEventInit : EventInit {
  required XRReferenceSpace referenceSpace;
  XRRigidTransform? transform = null;
};
</pre>

The <dfn attribute for="XRReferenceSpaceEvent">referenceSpace</dfn> attribute indicates the {{XRReferenceSpace}} that generated this event.

The <dfn attribute for="XRReferenceSpaceEvent">transform</dfn> attribute describes the post-event position and orientation of the {{XRReferenceSpaceEvent/referenceSpace}}'s [=native origin=] in the pre-event coordinate system.

Event Types {#event-types}
-----------

The user agent MUST provide the following new events. Registration for and firing of the events must follow the usual behavior of DOM4 Events.

The user agent MUST fire a <dfn event for="XRSystem">devicechange</dfn> event on the {{XRSystem}} object to indicate that the availability of [=immersive XR device=]s has been changed unless the document's origin is not allowed to use the "xr-spatial-tracking" [[#permissions-policy|permissions policy]]. The event MUST be of type {{Event}}.

A user agent MUST dispatch a <dfn event for="XRSession">visibilitychange</dfn> event on an {{XRSession}} each time the [=XRSession/visibility state=] of the {{XRSession}} has changed. The event MUST be of type {{XRSessionEvent}}.

A user agent MUST dispatch an <dfn event for="XRSession">end</dfn> event on an {{XRSession}} when the session ends, either by the application or the user agent. The event MUST be of type {{XRSessionEvent}}.

A user agent MUST dispatch an <dfn event for="XRSession">inputsourceschange</dfn> event on an {{XRSession}} when the session's [=list of active XR input sources=] has changed. The event MUST be of type {{XRInputSourcesChangeEvent}}.

A user agent MUST dispatch a <dfn event for="XRSession">selectstart</dfn> event on an {{XRSession}} when one of its {{XRInputSource}}s begins its [=primary action=]. The event MUST be of type {{XRInputSourceEvent}}.

A user agent MUST dispatch a <dfn event for="XRSession">selectend</dfn> event on an {{XRSession}} when one of its {{XRInputSource}}s ends its [=primary action=] or when an {{XRInputSource}} that has begun a [=primary action=] is disconnected. The event MUST be of type {{XRInputSourceEvent}}.

A user agent MUST dispatch a <dfn event for="XRSession">select</dfn> event on an {{XRSession}} when one of its {{XRInputSource}}s has fully completed a [=primary action=]. The event MUST be of type {{XRInputSourceEvent}}.

A user agent MUST dispatch a <dfn event for="XRSession">squeezestart</dfn> event on an {{XRSession}} when one of its {{XRInputSource}}s begins its [=primary squeeze action=]. The event MUST be of type {{XRInputSourceEvent}}.

A user agent MUST dispatch a <dfn event for="XRSession">squeezeend</dfn> event on an {{XRSession}} when one of its {{XRInputSource}}s ends its [=primary squeeze action=] or when an {{XRInputSource}} that has begun a [=primary squeeze action=] is disconnected. The event MUST be of type {{XRInputSourceEvent}}.

A user agent MUST dispatch a <dfn event for="XRSession">squeeze</dfn> event on an {{XRSession}} when one of its {{XRInputSource}}s has fully completed a [=primary squeeze action=]. The event MUST be of type {{XRInputSourceEvent}}.

A user agent MUST dispatch a <dfn event for="XRReferenceSpace">reset</dfn> event on an {{XRReferenceSpace}} when discontinuities of the [=native origin=] or [=effective origin=] occur, i.e. there are significant changes in the origin’s position or orientation relative to the user’s environment. (For example: After user recalibration of their XR device or if the XR device automatically shifts its origin after losing and regaining tracking.) A {{reset}} event MUST also be dispatched when the {{boundsGeometry}} changes for an {{XRBoundedReferenceSpace}}. A {{reset}} event MUST NOT be dispatched if the [=viewer=]'s pose experiences discontinuities but the {{XRReferenceSpace}}'s origin physical mapping remains stable, such as when the [=viewer=] momentarily loses and regains tracking within the same tracking area. A {{reset}} event also MUST NOT be dispatched as an {{unbounded}} reference space makes small adjustments to its [=native origin=] over time to maintain space stability near the user, if a significant discontinuity has not occurred. The event MUST be of type {{XRReferenceSpaceEvent}}, and MUST be dispatched prior to the execution of any [=XR animation frame=]s that make use of the new origin. A {{reset}} event MUST be dispatched on all offset reference spaces of a reference space that fires a {{reset}} event, and the {{boundsGeometry}} of offset {{XRBoundedReferenceSpace}}s should also be recomputed.

Note: This does mean that the session needs to hold on to strong references to any {{XRReferenceSpace}}s that have {{XRReferenceSpace/reset}} listeners.

Note: Jumps in [=viewer=] position can be handled by the application by observing the {{XRPose/emulatedPosition}} boolean. If a jump in [=viewer=] position coincides with {{XRPose/emulatedPosition}} switching from `true` to `false`, it indicates that the [=viewer=] has regained tracking and their new position represents a correction from the previously emulated values. For experiences without a "teleportation" mechanic, where the [=viewer=] can move through the virtual world without moving physically, this is generally the application's desired behavior. However, if an experience does provide a "teleportation" mechanic, it may be needlessly jarring to jump the [=viewer=]'s position back after tracking recovery. Instead, when such an application recovers tracking, it can simply resume the experience from the [=viewer=]'s current position in the virtual world by absorbing that sudden jump in position into its teleportation offset. To do so, the developer calls {{getOffsetReferenceSpace()}} to create a replacement reference space with its [=effective origin=] adjusted by the amount that the [=viewer=]'s position jumped since the previous frame.

Security, Privacy, and Comfort Considerations {#security}
=============================================

The WebXR Device API provides powerful new features which bring with them several unique privacy, security, and comfort risks that user agents must take steps to mitigate.

Sensitive Information {#sensitive-information-header}
---------------------

In the context of XR, <dfn>sensitive information</dfn> includes, but is not limited to, user-configurable data such as interpupillary distance (IPD) and sensor-based data such as {{XRPose}}s. All [=immersive sessions=] will expose some amount of sensitive data, due to the user's pose being necessary to render anything. However, in some cases, the same sensitive information will also be exposed via {{XRSessionMode/"inline"}} sessions.

User intention {#user-intention}
--------------

<dfn>User intent</dfn> for a given action is a signal from the user that such an action was intentional and has their consent.

It is often necessary to be sure of [=user intent=] before exposing sensitive information or allowing actions with a significant effect on the user's experience. This intent may be communicated or observed in a number of ways.

Note: A common way of determining user intent is by [=transient activation=] of a UI control, typically an "enter VR" button. Since activation is transient,
the [=browsing context=] requesting an XR session must be an [=ancestor=] or a [=same origin-domain=] [=descendant=] of the context containing the UI control, and must recently
have been the [=active document=] of the browsing context.

### User activation ### {#user-activation}
[=Transient activation=] MAY serve as an indication of [=user intent=] in some scenarios.

### Launching a web application ### {#application-launch}
In some environments a page may be presented as an application, installed with the express intent of running immersive content. In that case <dfn>launching a web application</dfn> MAY also serve as an indication of [=user intent=].

### Implicit and Explicit consent ### {#user-consent}

<dfn>Implicit consent</dfn> is when the user agent makes a judgement on the consent of a user without explicitly asking for it, for example, based on the install status of a web application or frequency and recency of visits. Given the sensitivity of XR data, caution is strongly advised when relying on implicit signals.

<dfn>Explicit consent</dfn> is when the user agent makes a judgement on the consent of a user based on having explicitly asked for it. When gathering [=explicit consent=], user agents present an explanation of what is being requested and provide users the option to decline. Requests for user consent can be presented in many visual forms based on the features being protected and user agent choice. Install status of a web application MAY count as a signal of [=explicit consent=] provided some form of [=explicit consent=] is requested at install time.

### Duration of consent ### {#consent-duration}
It is recommended that once [=explicit consent=] is granted for a specific [=/origin=] that this consent persist until the [=/browsing context=] has ended. User agents may choose to lengthen or shorten this consent duration based upon implicit or explicit signals of [=user intent=], but implementations are advised to exercise caution when deviating from this recommendation, particularly when relying on implicit signals. For example, it may be appropriate for a web application installed with the express intent of running immersive content to persist the user's consent, but not for an installed web application where immersive content is a secondary feature.

Regardless of how long the user agent chooses to persist the user's consent, [=sensitive information=] MUST only be exposed by an {{XRSession}} which has not [=ended=].

Mid-session consent {#mid-session-consent}
-------------------

There are multiple non-XR APIs which cause user agents to request [=explicit consent=] to use a feature. If the user agent will request the user's consent while there is an [=active immersive session=], the user agent MUST [=shut down the session=] prior to displaying the consent request to the user. If the user's consent for the feature had been granted prior to the [=active immersive session=] being created the session does not need to be terminated.

Note: This limitation is to ensure that there is behavioral parity between all user agents until consensus is reached about how user agents should manage mid-session [=explicit consent=]. It is not expected to be a long term requirement.

Data adjustments {#data-adjustments-header}
----------------

In some cases, security and privacy threats can be mitigated through <dfn>data adjustment</dfn>s such as throttling, quantizing, rounding, limiting, or otherwise manipulating the data reported from the [=/XR device=]. This may sometimes be necessary to avoid fingerprinting, even in situations when [=user intent=] has been established.  However, [=data adjustment=] mitigations MUST only be used in situations which would not result in user discomfort.

### Throttling ### {#throttling-header}
<dfn>Throttling</dfn> is when [=sensitive information=] is reported at a lower frequency than otherwise possible. This mitigation has the potential to reduce a site's ability to infer user intent, infer location, or perform user profiling. However, when not used appropriately throttling runs a significant risk of causing user discomfort. In addition, under many circumstances it may be inadequate to provide a complete mitigation.

### Rounding, quantization, and fuzzing ### {#rounding-and-friends}
Rounding, quantization, and fuzzing are three categories of mitigations that modify the raw data that would otherwise be returned to the developer. <dfn>Rounding</dfn> decreases the precision of data by reducing the number of digits used to express it. <dfn>Quantization</dfn> constrains continuous data to instead report a discrete subset of values. Fuzzing is the introduction of slight, random errors into the the data. Collectively, these mitigations are useful to avoid fingerprinting, and are especially useful when doing so does not cause noticeable impact on user comfort.

### Limiting ### {#limiting-header}
<dfn>Limiting</dfn> is when data is reported only when it is within a specific range. For example, it is possible to comfortably limit reporting positional pose data when a user has moved beyond a specific distance away from an approved location. Care should be taken to ensure that the user experience is not negatively affected when employing this mitigation. It is often desirable to avoid a 'hard stop' at the end of a range as this may cause disruptive user experiences.

Protected functionality {#protected-functionality}
-----------------------

The [=sensitive information=] exposed by the API can be divided into categories that share threat profiles and necessary protections against those threats.

### Immersiveness ### {#protect-immersiveness}
Users must be in control of when immersive sessions are created because the creation causes invasive changes on a user's machine. For example, starting an [=immersive session=] will engage the [=/XR device=] sensors, take over access to the device's display, and begin presentating immersive content which may terminate another application's access to the XR hardware. It may also incur significant power or performance overhead on some systems or trigger the launching of a status tray or storefront.

<div class="algorithm" data-algorithm="immersive-session-allowed">

To determine if an <dfn>immersive session request is allowed</dfn> for a given |global object| the user agent MUST run the following steps:

  1. If the request was not made while the |global object| has [=transient activation=] or when [=launching a web application=], return `false`
  1. If [=user intent=] to begin an [=immersive session=] is not well understood, either via [=explicit consent=] or [=implicit consent=], return `false`
  1. Return `true`

</div>

Starting an {{XRSessionMode/"inline"}} session does not implicitly carry the same requirements, though additional requirements may be imposed depending on the session's [=requested features=].

<div class="algorithm" data-algorithm="inline-session-allowed">

To determine if an <dfn>inline session request is allowed</dfn> for a given |global object| the user agent MUST run the following steps:

  1. If the session request contained any [=required features=] or [=optional features=] and the request was not made while the |global object| has [=transient activation=] or when [=launching a web application=], return `false`
  1. If the requesting document is not [=responsible=], return `false`
  1. Return `true`

</div>

### Poses ### {#protect-poses}
When based on sensor data, {{XRPose}} and {{XRViewerPose}} will expose [=sensitive information=] that may be misused in a number of ways, including input sniffing, gaze tracking, or fingerprinting.

<div class="algorithm" data-algorithm="poses-allowed">

To determine if <dfn>poses may be reported</dfn> to an {{XRSession}} |session|, the user agent MUST run the following steps:

  1. If |session|'s [=relevant global object=] is not the [=current global object=], return `false`.
  1. If |session|'s {{XRSession/visibilityState}} in not {{XRVisibilityState/"visible"}}, return `false`.
  1. Determine if the pose data can be returned as follows:
    <dl class="switch">
      : If the pose data is known by the user agent to not expose fingerprintable sensor data
      :: Return `true`.

      : If [=data adjustments=] will be applied to the underlying sensor data to prevent fingerprinting or profiling
      :: Return `true`.

      : If [=user intent=] is well understood, either via [=explicit consent=] or [=implicit consent=]
      :: Return `true`.

      : Otherwise
      :: Return `false`.

    </dl>


Note: The method by which a user agent determines that poses do not expose fingerprintable data is left to the user agent's discretion.

</div>

The primary difference between {{XRViewerPose}} and {{XRPose}} is the inclusion of {{XRView}} information. When more than one view is present and the physical relationship between these views is configurable by the user, the relationship between these views is considered [=sensitive information=] as it can be used to fingerprint or profile the user.

If the relationship between {{XRView}}s could uniquely identify the [=/XR device=], then the user agent MUST anonymize the {{XRView}} data to prevent fingerprinting. The method of anonymization is at the discretion of the user agent.

Note: Furthermore, if the relationship between {{XRView}}s is affected by a user-configured interpupillary distance (IPD), then it is strongly recommended that the user agent require [=explicit consent=] during session creation, prior to reporting any {{XRView}} data.

### Reference spaces ### {#protect-reference-spaces}
Depending on the reference spaces used, several different types of [=sensitive information=] may be exposed to the application.

 - On devices which support [=6DoF=] tracking, {{XRReferenceSpaceType/"local"}} reference spaces may be used to perform gait analysis, allowing user profiling and fingerprinting.

 - On devices which support [=6DoF=] tracking, {{XRReferenceSpaceType/"local-floor"}} reference spaces may be used to perform gait analysis, allowing user profiling and fingerprinting. In addition, because the {{XRReferenceSpaceType/"local-floor"}} reference spaces provide an established floor level, it may be possible for a site to infer the user's height, allowing user profiling and fingerprinting.

 - {{XRReferenceSpaceType/"bounded-floor"}} reference spaces, when sufficiently constrained in size, do not enable developers to determine geographic location. However, because the floor level is established and users are able to walk around, it may be possible for a site to infer the user's height or perform gait analysis, allowing user profiling and fingerprinting. In addition, it may be possible perform fingerprinting using the bounds reported by a bounded reference space.

 - {{XRReferenceSpaceType/"unbounded"}} reference spaces reveal the largest amount of spatial data and may result in user profiling and fingerprinting. For example, this data may enable determining user's specific geographic location or to perform gait analysis.

As a result the various reference space types have restrictions placed on their creation to ensure the [=sensitive information=] expose is handled safely:

Most reference spaces require that [=user intent=] to use the reference space is well understood, either via [=explicit consent=] or [=implicit consent=]. See the [=feature requirements=] table for details.

Any group of {{XRReferenceSpaceType/"local"}}, {{XRReferenceSpaceType/"local-floor"}}, and {{XRReferenceSpaceType/"bounded-floor"}} reference spaces that are capable of being related to one another MUST share a common [=native origin=]; This restriction only applies when the creation of {{XRReferenceSpaceType/"unbounded"}} reference spaces has been restricted.

<div class="algorithm" data-algorithm="poses-limited">

To determine if <dfn>poses must be limited</dfn> between two spaces, |space| and |baseSpace|, the user agent MUST run the following steps:

  1. If either |space| or |baseSpace| are an {{XRBoundedReferenceSpace}} and the other space's [=native origin=]'s falls further outside the [=native bounds geometry=] than a reasonable distance determined by the user agent, return true.
  1. If either |space| or |baseSpace| are an {{XRReferenceSpace}} with a [=XRReferenceSpace/type=] of {{XRReferenceSpaceType/"local"}} or {{XRReferenceSpaceType/"local-floor"}} and the distance between the spaces' [=native origin=]'s is greater than a reasonable distance determined by the user agent, return `true`.
  1. Return `false`.

</div>

Note: The requirement for document visibility is based on [[DEVICE-ORIENTATION]].

Note: Is is suggested that poses reported relative to a {{XRReferenceSpaceType/"local"}} or {{XRReferenceSpaceType/"local-floor"}} reference space be [=limiting|limited=] to a distance of 15 meters from the {{XRReferenceSpace}}'s [=native origin=].

Note: Is is suggested that poses reported relative to a {{XRBoundedReferenceSpace}} be [=limiting|limited=] to a distance of 1 meter outside the {{XRBoundedReferenceSpace}}'s [=native bounds geometry=].


Trusted Environment {#trustedenvironment-security}
-------------------

A <dfn>Trusted UI</dfn> is an interface presented by the User Agent that the user is able to interact with but the page cannot. The user agent MUST support showing [=trusted UI=].

A [=trusted UI=] MUST have the following properties:

    - It must not be spoofable
    - It indicates where the request/content displayed originates from
    - If it relies on a shared secret with the user, this shared secret cannot be observed by a mixed reality capture (e.g. it may not be a gesture that can be seen by the camera)
    - It is consistent between immersive experiences in the same UA

Broadly speaking, there are two options for user agents who wish to support [=trusted UI=]. One option is <dfn>trusted immersive UI</dfn>, which is a [=trusted UI=] which does not exit immersive mode. Implementing [=trusted immersive UI=] can be challenging because `XRWebGLLayer` buffers fill the XR Device display and the User Agent does not typically "reserve" pixels for its own use. User agents are not required to support [=trusted immersive UI=], they may instead temporarily pause/exit immersive mode and show non-immersive [=trusted UI=] to the user.

<div class="note">
Note: Examples of [=trusted UI=] include:
  - The default 2D mode browser shown when not in immersive mode
  - A prompt shown within immersive mode which can only be interacted with via a reserved hardware button to prevent spoofing
  - Pausing the immersive session and showing some form of native system environment in which a prompt can be shown

</div>

The ability to read input information (head pose, input pose, etc) poses a risk to the integrity of [=trusted UI=] as the page may use this information to snoop on the choices made by the user while interacting with the [=trusted UI=], including guessing keyboard input. To prevent this risk the user agent MUST set the [=visibility state=] of all {{XRSession}}s to {{XRVisibilityState/"hidden"}} or {{XRVisibilityState/"visible-blurred"}} when the user is interacting with [=trusted UI=] ([=trusted immersive ui|immersive=] or non-immersive) such as URL bars or system dialogs. Additionally, to prevent a malicious page from being able to monitor input on other pages the user agent MUST set the {{XRSession}}'s [=visibility state=] to {{XRVisibilityState/"hidden"}} if the [=currently focused area=] does not belong to the document which created the {{XRSession}}.

When choosing between using {{XRVisibilityState/"hidden"}} or {{XRVisibilityState/"visible-blurred"}} for a particular instance of [=trusted UI=], the user agent MUST consider whether head pose information is a security risk. For example, [=trusted UI=] involving text input, especially password inputs, can potentially leak the typed text through the user's head pose as they type. The user agent SHOULD also stop exposing any eye tracking-related information in such cases.

The user agent MUST use [=trusted UI=] to show permissions prompts.

If the virtual environment does not consistently track the user's head motion with low latency and at a high frame rate the user may become disoriented or physically ill. Since it is impossible to force pages to produce consistently performant and correct content the user agent MUST provide a tracked, trusted environment and an [=XR Compositor=] which runs asynchronously from page content. The compositor is responsible for compositing the trusted and untrusted content. If content is not performant, does not submit frames, or terminates unexpectedly the user agent should be able to continue presenting a responsive, [=trusted UI=].

Additionally, page content has the ability to make users uncomfortable in ways not related to performance. Badly applied tracking, strobing colors, and content intended to offend, frighten, or intimidate are examples of content which may cause the user to want to quickly exit the XR experience. Removing the XR device in these cases may not always be a fast or practical option. To accommodate this the user agent MUST provide users with an action, such as pressing a reserved hardware button or performing a gesture, that escapes out of WebXR content and displays the user agent's [=trusted UI=].

<section class="unstable">


Context Isolation {#contextisolation-security}
-----------------

The trusted UI must be drawn by an independent rendering context whose state is isolated from any rendering contexts used by the page. (For example, any WebGL rendering contexts.) This is to prevent the page from corrupting the state of the trusted UI's context, which may prevent it from properly rendering a tracked environment. It also prevents the possibility of the page being able to capture imagery from the trusted UI, which could lead to private information being leaked.

Also, to prevent CORS-related vulnerabilities each [=browsing context=] will see a new instance of objects returned by the API, such as {{XRSession}}. Attributes such as the [=XRWebGLLayer/context=] set on an {{XRWebGLLayer}} with one [=relevant realm=] should not be able to be read through an {{XRWebGLLayer}} with a [=relevant realm=] that does not have the [=same origin=]. Similarly, methods invoked on the API MUST NOT cause an observable state change on other [=browsing contexts=]. For example: No method will be exposed that enables a system-level orientation reset, as this could be called repeatedly by a malicious page to prevent other pages from tracking properly. The user agent MUST, however, respect system-level orientation resets triggered by a user gesture or system menu.

Note: this doesn't apply to state changes that are caused by one [=browsing context=] entering immersive mode, acquiring a lock on the device, and potentially firing {{devicechange}} events on other [=browsing contexts=].

Fingerprinting {#fingerprinting-security}
--------------

Given that the API describes hardware available to the user and its capabilities it will inevitably provide additional surface area for fingerprinting. While it's impossible to completely avoid this, user agents should take steps to mitigate the issue. This spec limits reporting of available hardware to only a single device at a time, which prevents using the rare cases of multiple headsets being connected as a fingerprinting signal. Also, the devices that are reported have no string identifiers and expose very little information about the devices capabilities until an XRSession is created, which requires additional protections when [=sensitive information=] will be exposed.
</section>

Integrations {#integrations}
============

Permissions Policy {#permissions-policy}
--------------

This specification defines a [=policy-controlled feature=] that controls whether any {{XRSession}} that requires the use of spatial tracking may be returned by {{XRSystem/requestSession()}}, and whether support for session modes that require spatial tracking may be indicated by either {{XRSystem/isSessionSupported()}} or {{devicechange}} events on the {{Navigator/xr|navigator.xr}} object.

The feature identifier for this feature is `"xr-spatial-tracking"`.

The [=default allowlist=] for this feature is `["self"]`.

Note: If the document's origin is not allowed to use the `"xr-spatial-tracking"` [=permissions policy=] any [=immersive sessions=] will be blocked, because all [=immersive sessions=] require some use of spatial tracking. [=Inline sessions=] will still be allowed, but restricted to only using the {{XRReferenceSpaceType/"viewer"}} {{XRReferenceSpace}}.


Permissions API Integration {#permissions}
---------

The [[!permissions]] API provides a uniform way for websites to request permissions from users and query which permissions they have been granted.

The <dfn enum-value for="PermissionName">"xr"</dfn> [=powerful feature=]’s permission-related algorithms and types are defined as follows:


<dl>
<dt>[=permission descriptor type=]</dt>
<dd>

<pre class="idl">
dictionary XRPermissionDescriptor: PermissionDescriptor {
  XRSessionMode mode;
  sequence&lt;any&gt; requiredFeatures;
  sequence&lt;any&gt; optionalFeatures;
};
</pre>

{{PermissionDescriptor/name}} for {{XRPermissionDescriptor}} is {{PermissionName/"xr"}}.


</dd>

<dt>[=permission result type=]</dt>
<dd>
<pre class=idl>
[Exposed=Window]
interface XRPermissionStatus: PermissionStatus {
  attribute FrozenArray&lt;any&gt; granted;
};
</pre>

</dd>


<dt>[=permission request algorithm=]</dt>
<dd>
<div class=algorithm data-algorithm="xr-permission-request-algorithm">
To <dfn lt="request the xr permission">request the "xr" permission</dfn> with an {{XRPermissionDescriptor}} |descriptor| and a {{XRPermissionStatus}} |status|, the UA MUST run the following steps:

  1. Set |status|'s {{XRPermissionStatus/granted}} to an empty {{FrozenArray}}.
  1. Let |requiredFeatures| be |descriptor|'s {{XRPermissionDescriptor/requiredFeatures}}.
  1. Let |optionalFeatures| be |descriptor|'s {{XRPermissionDescriptor/optionalFeatures}}.
  1. Let |device| be the result of [=obtain the current device|obtaining the current device=] for |mode|, |requiredFeatures|, and |optionalFeatures|.
  1. Let |result| be the result of [=resolve the requested features|resolving the requested features=] given |requiredFeatures|,|optionalFeatures|, and {{XRPermissionDescriptor/mode}}.
  1. If |result| is `null`, run the following steps:
    1. Set |status|'s {{PermissionStatus/state}} to {{PermissionState/"denied"}}.
    1. Abort these steps.
  1. Let (|consentRequired|, |consentOptional|, |granted|) be the fields of |result|.
  1. The user agent MAY at this point ask the user’s permission for the calling algorithm to use any of the features in |consentRequired| and |consentOptional|. The results of these prompts should be included when determining if there is a clear signal of [=user intent=] for enabling these features.
  1. For each |feature| in |consentRequired| perform the following steps:
      1. The user agent MAY at this point ask the user’s permission for the calling algorithm to use |feature|. The results of these prompts should be included when determining if there is a clear signal of [=user intent=] to enable |feature|.
      1. If a clear signal of [=user intent=] to enable |feature| has not been determined, set |status|'s {{PermissionStatus/state}} to {{PermissionState/"denied"}} and abort these steps.
      1. If |feature| is not in |granted|, append |feature| to |granted|.
  1. For each |feature| in |consentOptional| perform the following steps:
      1. The user agent MAY at this point ask the user’s permission for the calling algorithm to use |feature|. The results of these prompts should be included when determining if there is a clear signal of [=user intent=] to enable |feature|.
      1. If a clear signal of [=user intent=] to enable |feature| has not been determined, continue to the next entry.
      1. If |feature| is not in |granted|, append |feature| to |granted|.
  1. Set |status|'s {{XRPermissionStatus/granted}} to |granted|.
  1. Set |device|'s [=list of enabled features=] for |mode| to |granted|.
  1. Set |status|'s {{PermissionStatus/state}} to {{PermissionState/"granted"}}.

Note: The user agent has the freedom to batch up permissions prompts for all requested features when gauging if there is a clear signal of [=user intent=], but it is also allowed to show them one at a time.

</div>


<dt>[=permission query algorithm=]</dt>
<dd>
<div class=algorithm data-algorithm="xr-permission-query-algorithm">
To query the "xr" permission with an {{XRPermissionDescriptor}} |descriptor| and a {{XRPermissionStatus}} |status|, the UA MUST run the following steps:

  1. Set |status|'s {{PermissionStatus/state}} to |descriptor|'s [=permission state=].
  1. If |status|'s {{PermissionStatus/state}} is {{PermissionState/"denied"}}, set |status|'s {{XRPermissionStatus/granted}} to an empty {{FrozenArray}} and abort these steps.
  1. Let |result| be the result of [=resolve the requested features|resolving the requested features=] given |descriptor|'s {{XRPermissionDescriptor/requiredFeatures}}, {{XRPermissionDescriptor/optionalFeatures}}, and {{XRPermissionDescriptor/mode}}.
  1. If |result| is `null`, run the following steps:
    1. Set |status|'s {{XRPermissionStatus/granted}} to an empty {{FrozenArray}}.
    1. Set |status|'s {{PermissionStatus/state}} to {{PermissionState/"denied"}}.
    1. Abort these steps.
  1. Let (|consentRequired|, |consentOptional|, |granted|) be the fields of |result|.
  1. Set |status|'s {{XRPermissionStatus/granted}} to |granted|.
  1. If |consentRequired| [=list/is empty=] and |consentOptional| [=list/is empty=], set |status|'s {{PermissionStatus/state}} to {{PermissionState/"granted"}} and abort these steps
  1. Set |status|'s {{PermissionStatus/state}} to {{PermissionState/"prompt"}}.

</div>

</dd>

</dl>

<div class="algorithm" data-algorithm="resolve-features">

To <dfn>resolve the requested features</dfn> given |requiredFeatures| and |optionalFeatures| for an {{XRSessionMode}} |mode|, the user agent MUST run the following steps:

  1. Let |consentRequired| be an empty [=/list=] of {{DOMString}}.
  1. Let |consentOptional| be an empty [=/list=] of {{DOMString}}.
  1. Let |device| be the result of [=obtain the current device|obtaining the current device=] for |mode|, |requiredFeatures|, and |optionalFeatures|.
  1. Let |granted| be a [=/list=] of {{DOMString}} initialized to |device|'s [=XR device/list of enabled features=] for |mode|.
  1. If |device| is `null` or |device|'s [=list of supported modes=] does not [=list/contain=] |mode|, run the following steps:
    1. Return the [=tuple=] (|consentRequired|, |consentOptional|, |granted|)
  1. Add every [=feature descriptor=] in the [=default features=] table associated with |mode| to the indicated feature list if it is not already present.
  1. For each |feature| in |requiredFeatures| perform the following steps:
    1. If the |feature| is `null`, [=continue=] to the next entry.
    1. If |feature| is not a valid [=feature descriptor=], perform the following steps:
        1. Let |s| be the result of calling [=?=] <a abstract-op>ToString</a>(|feature|).
        1. If |s| is not a valid [=feature descriptor=] or is `undefined`, return `null`.
        1. Set |feature| to |s|.
    1. If |feature| is already in |granted|, continue to the next entry.
    1. If the requesting document's origin is not allowed to use any [[#permissions-policy|permissions policy]] required by |feature| as indicated by the [=feature requirements=] table, return `null`.
    1. If |session|'s [=XRSession/XR device=] is not [=capable of supporting=] the functionality described by |feature| or the user agent has otherwise determined to reject the feature, return `null`.
    1. If the functionality described by |feature| requires [=explicit consent=], append it to |consentRequired|.
    1. Else append |feature| to |granted|.
  1. For each |feature| in |optionalFeatures| perform the following steps:
    1. If the |feature| is `null`, [=continue=] to the next entry.
    1. If |feature| is not a valid [=feature descriptor=], perform the following steps:
        1. Let |s| be the result of calling [=?=] <a abstract-op>ToString</a>(|feature|).
        1. If |s| is not a valid [=feature descriptor=] or is `undefined`, [=continue=] to the next entry.
        1. Set |feature| to |s|.
    1. If |feature| is already in |granted|, continue to the next entry.
    1. If the requesting document's origin is not allowed to use any [[#permissions-policy|permissions policy]] required by |feature| as indicated by the [=feature requirements=] table, continue to the next entry.
    1. If |session|'s [=XRSession/XR device=] is not [=capable of supporting=] the functionality described by |feature| or the user agent has otherwise determined to reject the feature, continue to the next entry.
    1. If the functionality described by |feature| requires [=explicit consent=], append it to |consentOptional|.
    1. Else append |feature| to |granted|.
  1. Return the [=tuple=] `(|consentRequired|, |consentOptional|, |granted|)`

</div>

Acknowledgements {#ack}
================

Thank you to the following individuals for their contributions the WebXR Device API specification:

  * <a href="mailto:cvan@mozilla.com">Chris Van Wiemeersch</a> (<a href="https://mozilla.org/">Mozilla</a>)
  * <a href="mailto:kgilbert@mozilla.com">Kearwood Gilbert</a> (<a href="https://mozilla.org/">Mozilla</a>)
  * <a href="mailto:rafael.cintron@microsoft.com">Rafael Cintron</a> (<a href="https://microsoft.com/">Microsoft</a>)
  * <a href="mailto:sebastian.sylvan@gmail.com">Sebastian Sylvan</a> (Formerly <a href="https://microsoft.com/">Microsoft</a>)
  * <a href="mailto:blair@mozilla.com">Blair MacIntyre</a> (<a href="https://mozilla.org/">Mozilla</a>)
  * <a href="mailto:johnpallett@google.com">John Pallett</a> (<a href="https://www.google.com">Google</a>)
  * <a href="mailto:taoyagi@mozilla.com">Takahiro Aoyagi</a> (<a href="https://mozilla.org/">Mozilla</a>)
  * <a href="mailto:nb@8thwall.com">Nicholas Butko</a> (<a href="https://8thwall.com">8th Wall</a>)
  * <a href="mailto:artyom@fb.com">Artem Bolgar</a> (<a href="https://about.fb.com/company-info/">Facebook, Inc.</a>)
  * <a href="mailto:cwilso@google.com">Chris Wilson</a> (<a href="https://google.com/">Google</a>)
  * <a href="mailto:alan@metavrse.com">Alan Smithson</a> (<a href="https://MetaVRse.com">MetaVRse</a>)
  * <a href="mailto:jmarinacci@mozilla.com">Josh Marinacci</a> (<a href="https://mozilla.org/">Mozilla</a>)
  * <a href="mailto:loc@locdao.com">Loc Dao</a> (<a href="https://nfb.ca/interactive">National Film Board of Canada</a>)
  * <a href="mailto:ningxin.hu@intel.com">Ningxin Hu</a> (<a href="https://www.intel.com/">Intel</a>)
  * <a href="mailto:jared@plutovr.com">Jared Cheshier</a> (<a href="https://www.plutovr.com/">PlutoVR</a>)
  * <a href="mailto:thodgson@brainwaive.com">Tony Hodgson</a> (<a href="http://www.brainwaive.com">Brainwaive LLC</a>)
  * <a href="mailto:info@latorly.com">Christopher P. La Torres</a> (<a href="https://exokit.org">Exokit</a>)
  * <a href="mailto:alexis.menard@intel.com">Alexis Menard</a> (<a href="http://intel.com">Intel Corporation</a>)
  * <a href="mailto:klausw@google.com">Klaus Weidner</a> (<a href="https://google.com">Google</a>)
  * <a href="mailto:ajeffrey@mozilla.com">Alan Jeffrey</a> (<a href="https://mozilla.org/">Mozilla</a>)
  * <a href="mailto:dhosfelt@mozilla.com">Diane Hosfelt</a> (<a href="https://mozilla.org/">Mozilla</a>)
  * <a href="mailto:gminthe@gmail.com">Kyungsuk</a> (<a href="https://www.xandr.com/">AT&T Xandr</a>)
  * <a href="mailto:ddorwin@google.com">David Dorwin</a> (<a href="https://google.com">Google</a>)
  * <a href="mailto:trevor@transmutable.com">Trevor F. Smith</a> (<a href="https://transmutable.com/">Transmutable</a>)
  * <a href="mailto:ada.cannon@samsung.com">Ada Rose Cannon</a> (<a href="https://samsung.com">Samsung</a>)
  * <a href="mailto:mlamouri@google.com">Mounir Lamouri</a> (<a href="https://google.com">Google</a>)
  * <a href="mailto:stew@rtsmith.io">Stewart Smith</a> (<a href="https://moar.io">Moar Technologies Corp</a>)
  * <a href="mailto:web3d@realism.com">Leonard Daly</a> (<a href="https://realism.com">Daly Realism</a>)

And a special thanks to <a href="mailto:vladv@unity3d.com">Vladimir Vukicevic</a> (<a href="https://unity3d.com/">Unity</a>) for kick-starting this whole adventure!

</section>
